├── .gitignore ├── Client ├── Client.csproj ├── Program.cs └── example.p12 ├── NetworkProtocol.sln ├── NetworkProtocol ├── Makefile ├── NetworkProtocol.vcxproj ├── errors.c ├── example-com.cert.pem ├── example-com.key.pem ├── internal.h ├── network.c ├── network.h ├── platform.c ├── platform.h ├── protocol.c ├── tryout.c ├── utils.c └── vasprintf.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | /.vs 35 | /Debug 36 | /NetworkProtocol/Debug 37 | 38 | /NetworkProtocol/*.filters 39 | /NetworkProtocol/*.user 40 | /Client/bin/ 41 | /Client/obj/ 42 | /NetworkProtocol/np 43 | -------------------------------------------------------------------------------- /Client/Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7.1 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Security; 6 | using System.Net.Sockets; 7 | using System.Security.Cryptography.X509Certificates; 8 | using System.Threading.Tasks; 9 | 10 | namespace Client 11 | { 12 | class Program 13 | { 14 | static async Task Main(string[] args) 15 | { 16 | var tcpClient = new TcpClient(); 17 | await tcpClient.ConnectAsync(IPAddress.Loopback, 4433); 18 | using (var stream = tcpClient.GetStream()) 19 | using (var ssl = new SslStream(stream, leaveInnerStreamOpen: false, 20 | userCertificateValidationCallback: 21 | (object sender, X509Certificate certificate, 22 | X509Chain chain, SslPolicyErrors sslPolicyErrors) => true 23 | )) 24 | { 25 | var cert = new X509CertificateCollection(); 26 | cert.Add(new X509Certificate2(@"C:\Users\ayende\source\repos\ConsoleApplication4\Client\example.p12")); 27 | await ssl.AuthenticateAsClientAsync("example.com", cert, checkCertificateRevocation: false); 28 | 29 | var writer = new StreamWriter(ssl); 30 | var reader = new StreamReader(ssl); 31 | 32 | var status = reader.ReadLine(); 33 | if (status != "OK") 34 | { 35 | Console.WriteLine("Connection error: " + status); 36 | } 37 | 38 | 39 | writer.Write("GET employees/1-A\r\nSequence: 32\r\n\r\n"); 40 | writer.Flush(); 41 | 42 | var headers = new Dictionary(); 43 | status = reader.ReadLine(); 44 | 45 | string line; 46 | 47 | while ((line = reader.ReadLine()) != null && line.Length > 0) 48 | { 49 | var parts = line.Split(":"); 50 | headers[parts[0]] = parts[1].Trim(); 51 | } 52 | 53 | string val = null; 54 | if (headers.TryGetValue("Size", out var sizeStr) && int.TryParse(sizeStr, out var size)) 55 | { 56 | val = string.Create(size, reader, (span, state) => 57 | { 58 | state.ReadBlock(span); 59 | }); 60 | } 61 | 62 | if (status != "OK") 63 | { 64 | Console.WriteLine("ERROR! " + status); 65 | } 66 | 67 | Console.WriteLine(val); 68 | 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Client/example.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayende/generic-network-protocol/d4ec4c6ac0b88215bce0275fca30195b743618a9/Client/example.p12 -------------------------------------------------------------------------------- /NetworkProtocol.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28218.60 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NetworkProtocol", "NetworkProtocol\NetworkProtocol.vcxproj", "{72734FCF-0491-470C-9C46-021089CCF021}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{8A360EBD-B975-432F-AAB0-5E3858C31EF3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {72734FCF-0491-470C-9C46-021089CCF021}.Debug|Any CPU.ActiveCfg = Debug|Win32 21 | {72734FCF-0491-470C-9C46-021089CCF021}.Debug|x64.ActiveCfg = Debug|x64 22 | {72734FCF-0491-470C-9C46-021089CCF021}.Debug|x64.Build.0 = Debug|x64 23 | {72734FCF-0491-470C-9C46-021089CCF021}.Debug|x86.ActiveCfg = Debug|Win32 24 | {72734FCF-0491-470C-9C46-021089CCF021}.Debug|x86.Build.0 = Debug|Win32 25 | {72734FCF-0491-470C-9C46-021089CCF021}.Release|Any CPU.ActiveCfg = Release|Win32 26 | {72734FCF-0491-470C-9C46-021089CCF021}.Release|x64.ActiveCfg = Release|x64 27 | {72734FCF-0491-470C-9C46-021089CCF021}.Release|x64.Build.0 = Release|x64 28 | {72734FCF-0491-470C-9C46-021089CCF021}.Release|x86.ActiveCfg = Release|Win32 29 | {72734FCF-0491-470C-9C46-021089CCF021}.Release|x86.Build.0 = Release|Win32 30 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Debug|x64.ActiveCfg = Debug|Any CPU 33 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Debug|x64.Build.0 = Debug|Any CPU 34 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Debug|x86.Build.0 = Debug|Any CPU 36 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Release|x64.ActiveCfg = Release|Any CPU 39 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Release|x64.Build.0 = Release|Any CPU 40 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Release|x86.ActiveCfg = Release|Any CPU 41 | {8A360EBD-B975-432F-AAB0-5E3858C31EF3}.Release|x86.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {DF359DE8-B02C-4129-B921-29395B9841F7} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /NetworkProtocol/Makefile: -------------------------------------------------------------------------------- 1 | OPENSSL_PATH = ../.. 2 | 3 | build: tryout.c errors.c platform.c protocol.c network.c vasprintf.c utils.c 4 | gcc -std=gnu11 -o np tryout.c errors.c platform.c protocol.c network.c utils.c vasprintf.c -I ${OPENSSL_PATH}/openssl-1.1.1a/include/ -L ${OPENSSL_PATH}/openssl-1.1.1a -lssl -lcrypto -------------------------------------------------------------------------------- /NetworkProtocol/NetworkProtocol.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {72734FCF-0491-470C-9C46-021089CCF021} 24 | Win32Proj 25 | NetworkProtocol 26 | 10.0.17134.0 27 | NetworkProtocol 28 | 29 | 30 | 31 | Application 32 | true 33 | v141 34 | Unicode 35 | 36 | 37 | Application 38 | false 39 | v141 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | true 46 | v141 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v141 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | 88 | NotUsing 89 | Level3 90 | Disabled 91 | true 92 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | true 94 | 95 | 96 | false 97 | false 98 | true 99 | true 100 | false 101 | false 102 | CompileAsC 103 | 104 | 105 | Console 106 | true 107 | kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;Ws2_32.lib;%(AdditionalDependencies) 108 | 109 | 110 | 111 | 112 | Use 113 | Level3 114 | Disabled 115 | true 116 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 117 | true 118 | pch.h 119 | 120 | 121 | Console 122 | true 123 | 124 | 125 | 126 | 127 | Use 128 | Level3 129 | MaxSpeed 130 | true 131 | true 132 | true 133 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 134 | true 135 | pch.h 136 | 137 | 138 | Console 139 | true 140 | true 141 | true 142 | 143 | 144 | 145 | 146 | Use 147 | Level3 148 | MaxSpeed 149 | true 150 | true 151 | true 152 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 153 | true 154 | pch.h 155 | 156 | 157 | Console 158 | true 159 | true 160 | true 161 | 162 | 163 | 164 | 165 | true 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /NetworkProtocol/errors.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "network.h" 8 | 9 | 10 | thread_local_variable 11 | struct err* current_thread_err; 12 | 13 | 14 | void push_error_internal(const char* file, int line, const char *func, int code, const char* format, ...) { 15 | struct err* error; 16 | va_list ap; 17 | 18 | error = malloc(sizeof(struct err)); 19 | if (error == NULL) 20 | { 21 | // we are in a bad state, not much 22 | // we can do at this point, so let's bail 23 | return; 24 | } 25 | 26 | error->code = code; 27 | error->file = file; 28 | error->line = line; 29 | error->func = func; 30 | error->next = current_thread_err; 31 | 32 | va_start(ap, format); 33 | 34 | error->len = vasprintf(&error->msg, format, ap); 35 | 36 | if (error->len == -1) 37 | error->msg = NULL; 38 | 39 | va_end(ap); 40 | 41 | current_thread_err = error; 42 | } 43 | 44 | 45 | void consume_errors(error_callback cb, void * u) { 46 | while (current_thread_err != NULL) { 47 | struct err* cur = current_thread_err; 48 | current_thread_err = current_thread_err->next; 49 | if (cb != NULL) 50 | cb(cur, u); 51 | if (cur->len != -1 && cur->msg != NULL) 52 | free(cur->msg); 53 | free(cur); 54 | } 55 | } 56 | 57 | void print_error(struct err* e, void *u) { 58 | const char* file = strrchr(e->file, '/'); 59 | if (file == NULL) 60 | file = strrchr(e->file, '\\'); 61 | if (file == NULL) 62 | file = e->file; 63 | else 64 | file++;// move past the directory separator 65 | 66 | printf("%s:%i - %s() - %i %s\n", file, e->line, 67 | e->func, e->code, e->msg); 68 | } 69 | 70 | void print_all_errors(void) { 71 | consume_errors(print_error, NULL); 72 | } 73 | -------------------------------------------------------------------------------- /NetworkProtocol/example-com.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEVzCCAz+gAwIBAgIJAKEv7lW1gwDtMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIDAJOWTERMA8GA1UEBwwITmV3IFlvcmsxFTATBgNVBAoM 4 | DEV4YW1wbGUsIExMQzEYMBYGA1UEAwwPRXhhbXBsZSBDb21wYW55MR8wHQYJKoZI 5 | hvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MTEyNTE0MDY1NloXDTE5MTEy 6 | NTE0MDY1NlowfzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhO 7 | ZXcgWW9yazEVMBMGA1UECgwMRXhhbXBsZSwgTExDMRgwFgYDVQQDDA9FeGFtcGxl 8 | IENvbXBhbnkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wggEiMA0G 9 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZGQxRoA9adW773CuIchKxzfgxFpp4 10 | Yj24vuYH+j/VYtSmSso3Nn4jjak8NdosemISnyswPy6L91eMVLN/rz5/w6fYECTV 11 | IQPdzeZC6qhYt4qOALT2oD8h8O8cuS+z4ksYkTNI5W7PJOLo0OVZPx5mmg94EwHl 12 | BMHiclcmcdYz6wZpfjjSX9BRy7ILcPbninas53apt+DwF+yzjoDNaYKbExigDZiG 13 | uw5DNnRlWJhkkKUZPIg3HoBK7yhNXox/NUr3nGjUI+gAFWOhnNQhTJQOOxMRKvKq 14 | B1F10/5sz+grBmXo3nf9fqHuWVESAhRB1UvYSFDwzFpV55jo489Kry2fAgMBAAGj 15 | gdUwgdIwHQYDVR0OBBYEFCjXcrBziJRcHRzC6cZiQ4ZWDOtSMB8GA1UdIwQYMBaA 16 | FCjXcrBziJRcHRzC6cZiQ4ZWDOtSMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMEoG 17 | A1UdEQRDMEGCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb22CEG1haWwuZXhh 18 | bXBsZS5jb22CD2Z0cC5leGFtcGxlLmNvbTAsBglghkgBhvhCAQ0EHxYdT3BlblNT 19 | TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBAG96xbq0 20 | psphpxPL/uR2ZfVcmqr9pZdAqZ+M6GaEMs/fZORbK3/lA6oXxEdlkeNMk6GK0OKt 21 | NHdtCP7hnv++jrhSjr53fbHn86QNgPbMnuOS/8dDCuMVFuSu6W6pWyD2y3dE4zmL 22 | UZJlBL6SMqY4+mC/Hw3SyHu0jaEe/nJ3GJp1ZNAznwfxTo7oS1s5tq1HUI+xv37I 23 | Ic2wmblhQQOM8cfsqSRzRV1A2sakSgHysOiW0CgvcepluMza5h5ygUOqWKKTjE97 24 | 2Apsc7Oqg337nhkIE/B8yQx0dRhKunZ16T2wKMX6tUcVfDpktl4sIs7Usyaz1V6R 25 | AeCWOkDKcBcS2q8= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /NetworkProtocol/example-com.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZGQxRoA9adW77 3 | 3CuIchKxzfgxFpp4Yj24vuYH+j/VYtSmSso3Nn4jjak8NdosemISnyswPy6L91eM 4 | VLN/rz5/w6fYECTVIQPdzeZC6qhYt4qOALT2oD8h8O8cuS+z4ksYkTNI5W7PJOLo 5 | 0OVZPx5mmg94EwHlBMHiclcmcdYz6wZpfjjSX9BRy7ILcPbninas53apt+DwF+yz 6 | joDNaYKbExigDZiGuw5DNnRlWJhkkKUZPIg3HoBK7yhNXox/NUr3nGjUI+gAFWOh 7 | nNQhTJQOOxMRKvKqB1F10/5sz+grBmXo3nf9fqHuWVESAhRB1UvYSFDwzFpV55jo 8 | 489Kry2fAgMBAAECggEAFp8qjnLcRrPH7cyiaKRiWE67Fvpg/DxYbCDQPXKRJj4X 9 | JIDUk25FNJU1VkXSRyI1h/U3/d4HjqQmZkQJNDvhilSr77K289Jt4bXr0Xs8MYpm 10 | kKd4M9681V+Suu59DwR8iMHQkz79De6tCk+CJouSMmTJSdzJLMETJvJ9LE9eX6hj 11 | nYNT9myX/OTEvziW0pQC1Gly+IORPq6+sgfrFN3/RQo2z9my2aGuE3MOBhL7rVDU 12 | fgLrsOpKXhK/ST4qGRvN039lRYiWHW0+fy7SabhAfbkM9st9iqXBxLfrvV3kefuS 13 | mWZC7nFxQIM2NPNf928xPtP0/5m8Dxa0saK6RGXlgQKBgQD47svQUqIvgVC8nZ/g 14 | czPZ32kspSnFpxyPU+ezvT1hPsZHYVvLjyS/lWircaUM3j7W9SakXIfifeeXTGS8 15 | o205dPqypukSCy2uBd2NWYT1Ue0dXlrc9Qq9xJxHyb8EN9b6a5SGEjs80IaDjCBD 16 | wzmHpaRBVToToOen6QmtWerGQQKBgQDfQuFv0Xq8T8vBzzu8tC9sUOAdyMfFWuax 17 | 9AC6g9raW8HSB5tRzzGNgKoUVXe5jDk4iRkWbiN7j7vULmS2bpXDVpNwCnJxQ/1X 18 | pkTlXk2qlRwwQl8pKFxqg3UZqtbw7NkaQXbqzri0Lbz1VAow9Myy/Y0prYmqur5/ 19 | Noezr5u73wKBgHR78Z2C/WezYF9Sdvyli87YzzNX+gsGXsPm5FZkKDO4FzpRoY3I 20 | Zs7LkFYhcLVrzgXyY2mn7uIaPmO7GKx44ORxC9rLZebOmkqDbh/1ktTkyErk2ynn 21 | 9GXGecbR7fOAWbWG39I498VGYptt1689zE7gQNNdmTaUJbKZxrB6kfTBAoGBALuu 22 | qH7slYX6eqp5gJlYy8j9j/nZ0H5KtUaBfZ9Nusv3eM8MD/jM+bYfpFqlopj7lRq+ 23 | vbVKb0+u+9IaEhX+jq2AeT8luSEYa7+kCaTcCuRMpz62fPLHeDEeQ3GJWVl9ceCN 24 | id9IWqM1E/UUaeDP2cjaNzIDLYi1pfChMaDPDlb3AoGBAMIMXoEpzMkpnQx2zK2F 25 | iB9/evEuc64hYNzn4IyqeSrTBKNhZVS11MJg+ZcZGFMjmArcRXrKua1gHRo4WgVD 26 | q4LhjvZ+FAoy37rP17+EdGvSiSfuviagF10AaFZn/akILs36rFmnVgnblSC+5HFW 27 | Bne9R5SkCvmuvCe1iPEcMggt 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /NetworkProtocol/internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "network.h" 4 | #include "internal.h" 5 | 6 | typedef struct server_state { 7 | SSL_CTX* ctx; 8 | uv_loop_t* loop; 9 | server_state_init_t options; 10 | tls_uv_connection_state_t* pending_writes; 11 | } server_state_t; 12 | 13 | 14 | 15 | int read_message(tls_uv_connection_state_t* c, void* buffer, int nread); 16 | 17 | int connection_write(tls_uv_connection_state_t* c, void* buf, size_t len); 18 | 19 | int connection_write_format(tls_uv_connection_state_t* c, const char* format, ...); 20 | 21 | char * strnstr(const char *s, const char *find, size_t slen); 22 | -------------------------------------------------------------------------------- /NetworkProtocol/network.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "network.h" 6 | #include 7 | #include "internal.h" 8 | #include 9 | #include 10 | 11 | int maybe_flush_ssl(tls_uv_connection_state_t* state); 12 | 13 | static int verify_ssl_x509_certificate_callback(int preverify_ok, X509_STORE_CTX *ctx) 14 | { 15 | // we want to give good errors, so we acccept all certs 16 | // and validate them manually 17 | return 1; 18 | } 19 | 20 | int configure_context(SSL_CTX *ctx, const char* cert, const char* key) 21 | { 22 | if (SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM) <= 0) { 23 | push_ssl_errors(); 24 | push_error(EINVAL, "Unable register certificiate file: %s", cert); 25 | return 0; 26 | } 27 | 28 | if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) { 29 | push_ssl_errors(); 30 | push_error(EINVAL, "Unable register key file: %s", key); 31 | return 0; 32 | } 33 | 34 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_ssl_x509_certificate_callback); 35 | 36 | return 1; 37 | } 38 | 39 | server_state_t* server_state_create(server_state_init_t* options) { 40 | server_state_t* state; 41 | const SSL_METHOD *method; 42 | static int first_time_init_done; 43 | 44 | if (!first_time_init_done) { 45 | int rc = network_one_time_init(); 46 | if (rc != 0) { 47 | push_error(rc, "Unable to initialize network properly %i", rc); 48 | return NULL; 49 | } 50 | 51 | SSL_load_error_strings(); 52 | ERR_load_BIO_strings(); 53 | ERR_load_crypto_strings(); 54 | OpenSSL_add_ssl_algorithms(); 55 | first_time_init_done = 1; 56 | } 57 | 58 | state = calloc(1, sizeof(struct server_state)); 59 | if (state == NULL) { 60 | push_error(ENOMEM, "Unable to allocate server state"); 61 | return NULL; 62 | } 63 | 64 | state->options = *options; 65 | 66 | method = get_server_method(); 67 | 68 | if (method == NULL) { 69 | free(state); 70 | push_ssl_errors(); 71 | push_error(EINVAL, "Unable to create TLS 1.2 server method"); 72 | return NULL; 73 | } 74 | 75 | state->ctx = SSL_CTX_new(method); 76 | if (state->ctx == NULL) { 77 | free(state); 78 | push_ssl_errors(); 79 | push_error(EINVAL, "Unable to SSL ctx"); 80 | return NULL; 81 | } 82 | 83 | if (!configure_context(state->ctx, options->cert, options->key)) { 84 | server_state_drop(state); 85 | push_ssl_errors(); 86 | push_error(EINVAL, "Unable to configure SSL ctx with provided cert(%s) / key (%s)", options->cert, options->key); 87 | return NULL; 88 | } 89 | 90 | return state; 91 | } 92 | 93 | 94 | void server_state_drop(server_state_t* s) { 95 | SSL_CTX_free(s->ctx); 96 | 97 | free(s); 98 | } 99 | 100 | int validate_connection_certificate(tls_uv_connection_state_t* c) 101 | { 102 | int free_err = 0; 103 | ASN1_TIME* time; 104 | int day, sec, len, rc = 0; 105 | unsigned char digest[SHA_DIGEST_LENGTH]; 106 | char digest_hex[THUMBPRINT_HEX_LENGTH]; 107 | char*err = NULL; 108 | 109 | X509* client_cert = SSL_get_peer_certificate(c->ssl); 110 | if (client_cert == NULL) { 111 | err = "No certificate was sent, but this is required, aborting."; 112 | goto error; 113 | } 114 | time = X509_get_notAfter(client_cert); 115 | if (!ASN1_TIME_diff(&day, &sec, NULL, time)) { 116 | push_ssl_errors(); 117 | err = "Invalid certificate time - NotAfter"; 118 | goto error; 119 | } 120 | if (day < 0 || sec < 0) { 121 | err = "Certificate expired"; 122 | goto error; 123 | } 124 | time = X509_get_notBefore(client_cert); 125 | if (!ASN1_TIME_diff(&day, &sec, NULL, time)) { 126 | push_ssl_errors(); 127 | err = "Invalid certificate time - NotBefore"; 128 | goto error; 129 | } 130 | if (day > 0 || sec > 0) { 131 | err = "Certificate isn't valid yet"; 132 | goto error; 133 | } 134 | 135 | if (!X509_digest(client_cert, EVP_sha1(), digest, &len)) { 136 | err = "Failed to compute certificate digest"; 137 | goto error; 138 | } 139 | 140 | for (int i = 0; i < SHA_DIGEST_LENGTH; i++) { 141 | int rc = snprintf(digest_hex + (i * 2), 3/* for the null terminator*/, "%02X", digest[i]); 142 | if (rc != 2) { 143 | err = "Failed to format certificate digest"; 144 | goto error; 145 | } 146 | } 147 | for (int i = 0; i < c->server->options.known_thumprints_count; i++) 148 | { 149 | if (strncasecmp(digest_hex, c->server->options.known_thumprints[i], THUMBPRINT_HEX_LENGTH) == 0) 150 | { 151 | rc = 1; 152 | goto done; 153 | } 154 | } 155 | 156 | rc = asprintf(&err, "Unfamiliar certificate %s", digest_hex); 157 | if (rc == -1) 158 | err = "Unfamiliar cert"; 159 | else 160 | free_err = 1; 161 | 162 | error: 163 | connection_write_format(c, "ERR: %s\r\n", err); // notify remote 164 | push_error(EINVAL, err); // notify locally; 165 | if (free_err) 166 | free(err); 167 | rc = 0; 168 | done: 169 | if (client_cert != NULL) 170 | X509_free(client_cert); 171 | return rc; 172 | } 173 | 174 | int connection_write(tls_uv_connection_state_t* c, void* buf, size_t len) { 175 | int rc = SSL_write(c->ssl, buf, len); 176 | if (rc <= 0) { 177 | push_error(ENETRESET, "Unable to write message to connection: %i", SSL_get_error(c->ssl, rc)); 178 | return 0; 179 | } 180 | maybe_flush_ssl(c); 181 | return 1; 182 | } 183 | 184 | int connection_write_format(tls_uv_connection_state_t* c, const char* format, ...) { 185 | va_list ap; 186 | int rc; 187 | va_start(ap, format); 188 | char * msg; 189 | int len = vasprintf(&msg, format, ap); 190 | va_end(ap); 191 | 192 | if (len == -1) { 193 | push_error(EINVAL, "Failed to format message to write to connection"); 194 | return 0; 195 | } 196 | 197 | rc = connection_write(c, msg, len); 198 | free(msg); 199 | return rc; 200 | } 201 | 202 | void remove_connection_from_queue(tls_uv_connection_state_t* cur) { 203 | if (cur->pending.pending_writes_buffer != NULL) { 204 | free(cur->pending.pending_writes_buffer); 205 | } 206 | if (cur->pending.prev_holder != NULL) { 207 | *cur->pending.prev_holder = cur->pending.next; 208 | } 209 | 210 | memset(&cur->pending, 0, sizeof(cur->pending)); 211 | } 212 | 213 | void abort_connection_on_error(tls_uv_connection_state_t* state) { 214 | uv_close((uv_handle_t*)state->handle, NULL); 215 | SSL_free(state->ssl); 216 | remove_connection_from_queue(state); 217 | free(state); 218 | } 219 | 220 | void complete_write(uv_write_t* r, int status) { 221 | tls_uv_connection_state_t* state = r->data; 222 | free(r->write_buffer.base); 223 | free(r); 224 | 225 | if (status < 0) { 226 | push_error(status, "Failed to write to connection"); 227 | } 228 | else if (state->flags & CONNECTION_STATUS_WRITE_AND_ABORT) { 229 | push_error(status, "Done writing buffered error message, now aborting connection"); 230 | } 231 | else { 232 | return; 233 | } 234 | 235 | 236 | state->server->options.handler->connection_error(state); 237 | abort_connection_on_error(state); 238 | 239 | 240 | } 241 | 242 | 243 | int flush_ssl_buffer(tls_uv_connection_state_t* cur) { 244 | int rc = BIO_pending(cur->write); 245 | if (rc > 0) { 246 | void* mem = malloc(rc); 247 | if (mem == NULL) { 248 | push_error(ENOMEM, "Unable to allocate memory to flush SSL"); 249 | return 0; 250 | } 251 | uv_buf_t buf = uv_buf_init(mem, rc); 252 | rc = BIO_read(cur->write, buf.base, rc); 253 | if (rc <= 0) 254 | { 255 | free(mem); 256 | return 1;// nothing to read, that is fine 257 | } 258 | uv_write_t* r = calloc(1, sizeof(uv_write_t)); 259 | if (r == NULL) { 260 | push_error(ENOMEM, "Unable to allocate memory to flush SSL"); 261 | free(r); 262 | return 0; 263 | } 264 | r->data = cur; 265 | rc = uv_write(r, (uv_stream_t*)cur->handle, &buf, 1, complete_write); 266 | if (rc < 0) { 267 | push_libuv_error(rc, "uv_write"); 268 | free(r); 269 | free(mem); 270 | return 0; 271 | } 272 | } 273 | return 1; 274 | } 275 | 276 | 277 | void try_flush_ssl_state(uv_handle_t * handle) { 278 | server_state_t* server_state = handle->data; 279 | tls_uv_connection_state_t** head = &server_state->pending_writes; 280 | int rc; 281 | while (*head != NULL) { 282 | tls_uv_connection_state_t* cur = *head; 283 | 284 | rc = flush_ssl_buffer(cur); 285 | 286 | if (rc == 0) { 287 | push_error(rc, "Failed to flush SSL buffer"); 288 | server_state->options.handler->connection_error(cur); 289 | abort_connection_on_error(cur); 290 | continue; 291 | } 292 | 293 | if (cur->pending.pending_writes_count == 0) { 294 | remove_connection_from_queue(cur); 295 | continue; 296 | } 297 | 298 | // here we have pending writes to deal with, so we'll try stuffing them 299 | // into the SSL buffer 300 | int used = 0; 301 | for (size_t i = 0; i < cur->pending.pending_writes_count; i++) 302 | { 303 | int rc = SSL_write(cur->ssl, 304 | cur->pending.pending_writes_buffer[i].base, 305 | cur->pending.pending_writes_buffer[i].len); 306 | if (rc > 0) { 307 | used++; 308 | continue; 309 | } 310 | rc = SSL_get_error(cur->ssl, rc); 311 | if (rc == SSL_ERROR_WANT_WRITE) { 312 | flush_ssl_buffer(cur); 313 | i--;// retry 314 | continue; 315 | } 316 | if (rc != SSL_ERROR_WANT_READ) { 317 | push_ssl_errors(); 318 | server_state->options.handler->connection_error(cur); 319 | abort_connection_on_error(cur); 320 | cur->pending.in_queue = 0; 321 | break; 322 | } 323 | // we are waiting for reads from the network 324 | // we can't remove this instance, so we play 325 | // with the pointer and start the scan/remove 326 | // from this position 327 | head = &cur->pending.next; 328 | break; 329 | } 330 | rc = flush_ssl_buffer(cur); 331 | if (rc == 0) { 332 | push_error(rc, "Failed to flush SSL buffer"); 333 | server_state->options.handler->connection_error(cur); 334 | abort_connection_on_error(cur); 335 | continue; 336 | } 337 | if (used == cur->pending.pending_writes_count) { 338 | remove_connection_from_queue(cur); 339 | } 340 | else { 341 | cur->pending.pending_writes_count -= used; 342 | memmove(cur->pending.pending_writes_buffer, 343 | cur->pending.pending_writes_buffer + sizeof(uv_buf_t)*used, 344 | sizeof(uv_buf_t) * cur->pending.pending_writes_count); 345 | } 346 | } 347 | } 348 | 349 | void prepare_if_need_to_flush_ssl_state(uv_prepare_t * handle) { 350 | try_flush_ssl_state((uv_handle_t*)handle); 351 | } 352 | void check_if_need_to_flush_ssl_state(uv_check_t * handle) { 353 | try_flush_ssl_state((uv_handle_t*)handle); 354 | } 355 | 356 | void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 357 | buf->base = (char*)malloc(suggested_size); 358 | buf->len = suggested_size; 359 | } 360 | 361 | int maybe_flush_ssl(tls_uv_connection_state_t* state) { 362 | if (state->pending.in_queue) 363 | return 1; 364 | if (BIO_pending(state->write) == 0 && state->pending.pending_writes_count == 0) 365 | return 0; 366 | state->pending.next = state->server->pending_writes; 367 | if (state->pending.next != NULL) { 368 | state->pending.next->pending.prev_holder = &state->pending.next; 369 | } 370 | state->pending.prev_holder = &state->server->pending_writes; 371 | state->pending.in_queue = 1; 372 | 373 | state->server->pending_writes = state; 374 | return 1; 375 | } 376 | 377 | int ensure_connection_intialized(tls_uv_connection_state_t* state) { 378 | if (state->flags & CONNECTION_STATUS_INIT_DONE) 379 | return 1; 380 | 381 | if (SSL_is_init_finished(state->ssl)) { 382 | state->flags |= CONNECTION_STATUS_INIT_DONE; 383 | if (validate_connection_certificate(state) == 0) { 384 | state->flags |= CONNECTION_STATUS_WRITE_AND_ABORT; 385 | return 0; 386 | } 387 | return connection_write(state, "OK\r\n", 4); 388 | } 389 | 390 | return 1; 391 | } 392 | 393 | void handle_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { 394 | tls_uv_connection_state_t* state = client->data; 395 | if (nread <= 0) { 396 | push_libuv_error(nread, "Unable to read"); 397 | state->server->options.handler->connection_error(state); 398 | abort_connection_on_error(state); 399 | return; 400 | } 401 | 402 | 403 | int rc = BIO_write(state->read, buf->base, nread); 404 | assert(rc == nread); 405 | while (1) 406 | { 407 | int rc = SSL_read(state->ssl, buf->base, buf->len); 408 | if (rc <= 0) { 409 | rc = SSL_get_error(state->ssl, rc); 410 | if (rc != SSL_ERROR_WANT_READ) { 411 | push_ssl_errors(); 412 | state->server->options.handler->connection_error(state); 413 | abort_connection_on_error(state); 414 | break; 415 | } 416 | 417 | maybe_flush_ssl(state); 418 | ensure_connection_intialized(state); 419 | // need to read more, we'll let libuv handle this 420 | break; 421 | } 422 | 423 | // should be rare: can only happen if we go for 0rtt or something like that 424 | // and we do the handshake and have real data in one network roundtrip 425 | if (ensure_connection_intialized(state) == 0) 426 | break; 427 | 428 | if (state->flags & CONNECTION_STATUS_WRITE_AND_ABORT) { 429 | // we won't accept anything from this kind of connection 430 | // just read it out of the network and let's give the write 431 | // a chance to kill it 432 | continue; 433 | } 434 | if (read_message(state, buf->base, rc) == 0) { 435 | // handler asked to close the socket 436 | if (maybe_flush_ssl(state)) { 437 | state->flags |= CONNECTION_STATUS_WRITE_AND_ABORT; 438 | break; 439 | } 440 | abort_connection_on_error(state); 441 | break; 442 | } 443 | } 444 | 445 | free(buf->base); 446 | } 447 | 448 | void on_new_connection(uv_stream_t *server, int status) { 449 | uv_tcp_t *client = NULL; 450 | tls_uv_connection_state_t* state = NULL; 451 | server_state_t* server_state = server->data; 452 | if (status < 0) { 453 | push_libuv_error(status, "Unable to accept new connection"); 454 | goto error_handler; 455 | } 456 | 457 | client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t)); 458 | if (client == NULL) { 459 | push_error(ENOMEM, "Unable to allocate memory for new connection"); 460 | goto error_handler; 461 | } 462 | int rc = uv_tcp_init(server_state->loop, client); 463 | if (rc < 0) { 464 | push_libuv_error(rc, "uv_tcp_init"); 465 | goto error_handler; 466 | } 467 | status = uv_accept(server, (uv_stream_t*)client); 468 | if (status != 0) { 469 | push_libuv_error(rc, "uv_tcp_init"); 470 | goto error_handler; 471 | } 472 | state = server_state->options.handler->create_connection(); 473 | if (state == NULL) { 474 | push_error(ENOMEM, "create_connection callback returned NULL"); 475 | goto error_handler; 476 | } 477 | memset(state, 0, sizeof(struct tls_uv_connection_state_private_members)); 478 | state->ssl = SSL_new(server_state->ctx); 479 | if (state->ssl == NULL) { 480 | push_error(ENOMEM, "Unable to allocate SSL for connection"); 481 | goto error_handler; 482 | } 483 | SSL_set_accept_state(state->ssl); 484 | state->server = server_state; 485 | state->handle = client; 486 | state->read = BIO_new(BIO_s_mem()); 487 | state->write = BIO_new(BIO_s_mem()); 488 | if (state->read == NULL || state->write == NULL) { 489 | push_error(ENOMEM, "Unable to allocate I/O for connection"); 490 | goto error_handler; 491 | } 492 | 493 | BIO_set_nbio(state->read, 1); 494 | BIO_set_nbio(state->write, 1); 495 | SSL_set_bio(state->ssl, state->read, state->write); 496 | 497 | client->data = state; 498 | 499 | rc = uv_read_start((uv_stream_t*)client, alloc_buffer, handle_read); 500 | if (rc < 0) { 501 | push_libuv_error(rc, "uv_read_start"); 502 | goto error_handler; 503 | } 504 | 505 | return; 506 | 507 | error_handler: 508 | 509 | if (client != NULL) { 510 | uv_close((uv_handle_t*)client, NULL); 511 | free(client); 512 | } 513 | if (state != NULL) { 514 | if (state->ssl != NULL) { 515 | if (SSL_get_rbio(state->ssl) != NULL) 516 | state->read = NULL; 517 | if (SSL_get_wbio(state->ssl) != NULL) 518 | state->write = NULL; 519 | SSL_free(state->ssl); 520 | } 521 | if (state->read != NULL) { 522 | BIO_free(state->read); 523 | } 524 | if (state->write != NULL) { 525 | BIO_free(state->write); 526 | } 527 | } 528 | server_state->options.handler->failed_connection(); 529 | } 530 | 531 | int server_state_run(server_state_t* s) { 532 | 533 | s->loop = uv_default_loop(); 534 | if (s->loop == NULL) { 535 | push_error(ENOMEM, "Unable to allocate a uv loop"); 536 | goto error_cleanup; 537 | } 538 | 539 | uv_tcp_t server; 540 | int rc = uv_tcp_init(s->loop, &server); 541 | if (rc != 0) { 542 | push_libuv_error(rc, "uv_tcp_init"); 543 | goto error_cleanup; 544 | } 545 | server.data = s; 546 | struct sockaddr_in addr; 547 | rc = uv_ip4_addr(s->options.address, s->options.port, &addr); 548 | if (rc != 0) { 549 | push_libuv_error(rc, "uv_ip4_addr(%s, %i, addr)", s->options.address, s->options.port); 550 | goto error_cleanup; 551 | } 552 | 553 | rc = uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0); 554 | if (rc != 0) { 555 | push_libuv_error(rc, "uv_tcp_bind(%s : %i)", s->options.address, s->options.port); 556 | goto error_cleanup; 557 | } 558 | rc = uv_listen((uv_stream_t*)&server, 128, on_new_connection); 559 | if (rc != 0) { 560 | push_libuv_error(rc, "uv_listen(%s : %i)", s->options.address, s->options.port); 561 | goto error_cleanup; 562 | } 563 | uv_prepare_t before_io; 564 | before_io.data = s; 565 | rc = uv_prepare_init(s->loop, &before_io); 566 | if (rc != 0) { 567 | push_libuv_error(rc, "uv_prepare_init"); 568 | goto error_cleanup; 569 | } 570 | rc = uv_prepare_start(&before_io, prepare_if_need_to_flush_ssl_state); 571 | if (rc != 0) { 572 | push_libuv_error(rc, "uv_prepare_start"); 573 | goto error_cleanup; 574 | } 575 | uv_check_t after_io; 576 | after_io.data = s; 577 | rc = uv_check_init(s->loop, &after_io); 578 | if (rc != 0) { 579 | push_libuv_error(rc, "uv_check_init"); 580 | goto error_cleanup; 581 | } 582 | rc = uv_check_start(&after_io, check_if_need_to_flush_ssl_state); 583 | if (rc != 0) { 584 | push_libuv_error(rc, "uv_check_start"); 585 | goto error_cleanup; 586 | } 587 | 588 | rc = uv_run(s->loop, UV_RUN_DEFAULT); 589 | 590 | error_cleanup: 591 | 592 | if (s->loop != NULL) { 593 | uv_loop_close(s->loop); 594 | } 595 | 596 | return rc; 597 | } 598 | 599 | 600 | int push_single_ssl_error(const char * str, size_t len, void * _) { 601 | push_error(EINVAL, "%.*s", len, str);//write a size terminated string 602 | return 1; 603 | } 604 | 605 | void push_ssl_errors() { 606 | ERR_print_errors_cb(push_single_ssl_error, NULL); 607 | } 608 | 609 | void push_libuv_error(int rc, const char* operation, ...) { 610 | char tmp[256]; 611 | uv_strerror_r(rc, tmp, 256); 612 | 613 | va_list ap; 614 | va_start(ap, operation); 615 | char* msg; 616 | if (vasprintf(&msg, operation, ap) != -1) { 617 | operation = msg; 618 | } 619 | else { 620 | msg = NULL; 621 | } 622 | va_end(ap); 623 | 624 | push_error(rc, "libuv err: %s failed with %i - %s", msg, rc, tmp); 625 | if(msg != NULL) 626 | free(msg); 627 | } 628 | -------------------------------------------------------------------------------- /NetworkProtocol/network.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define _WITH_DPRINTF 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "platform.h" 11 | 12 | typedef struct server_state server_state_t; 13 | typedef struct connection_handler connection_handler_t; 14 | typedef struct err err_t; 15 | typedef struct tls_uv_connection_state tls_uv_connection_state_t; 16 | 17 | #define CONNECTION_STATUS_INIT_DONE 0x1 18 | #define CONNECTION_STATUS_WRITE_AND_ABORT 0x2 19 | 20 | struct tls_uv_connection_state_private_members { 21 | server_state_t* server; 22 | uv_tcp_t* handle; 23 | SSL *ssl; 24 | BIO *read, *write; 25 | struct { 26 | tls_uv_connection_state_t** prev_holder; 27 | tls_uv_connection_state_t* next; 28 | int in_queue; 29 | size_t pending_writes_count; 30 | uv_buf_t* pending_writes_buffer; 31 | } pending; 32 | size_t used_buffer, to_scan; 33 | int flags; 34 | }; 35 | 36 | #define RESERVED_SIZE (64 - sizeof(struct tls_uv_connection_state_private_members)) 37 | #define MSG_SIZE (8192 - sizeof(struct tls_uv_connection_state_private_members) - 64 - RESERVED_SIZE) 38 | 39 | 40 | // This struct is exactly 8KB in size, this 41 | // means it is two OS pages and is easy to work with 42 | typedef struct tls_uv_connection_state { 43 | struct tls_uv_connection_state_private_members; 44 | char reserved[RESERVED_SIZE]; 45 | char user_data[64]; // location for user data, 64 bytes aligned, 64 in size 46 | char buffer[MSG_SIZE]; 47 | } tls_uv_connection_state_t; 48 | 49 | // char(*__kaboom)[MSG_SIZE] = 1; 50 | 51 | static_assert(offsetof(tls_uv_connection_state_t, user_data) % 64 == 0, "tls_uv_connection_state_t.user should be 64 bytes aligned"); 52 | static_assert(sizeof(tls_uv_connection_state_t) == 8192, "tls_uv_connection_state_t should be 8KB"); 53 | 54 | // commands 55 | 56 | typedef struct header { 57 | char* key; 58 | char* value; 59 | } header_t; 60 | 61 | typedef struct cmd { 62 | tls_uv_connection_state_t* connection; 63 | char** argv; 64 | int argc; 65 | struct header* headers; 66 | int headers_count; 67 | char* sequence; 68 | 69 | char* cmd_buffer; 70 | } cmd_t; 71 | 72 | void cmd_drop(cmd_t * cmd); 73 | 74 | // error handling 75 | 76 | typedef struct err { 77 | err_t* next; 78 | char* msg; 79 | size_t len; 80 | int code; 81 | 82 | const char* file, *func; 83 | int line; 84 | } err_t; 85 | 86 | void push_error_internal(const char* file, int line, const char *func, int code, const char* format, ...); 87 | 88 | #define push_error(code, format, ...) push_error_internal(__FILE__, __LINE__, __func__, code, format, ##__VA_ARGS__) 89 | 90 | typedef void(*error_callback)(err_t* e, void * u); 91 | 92 | void consume_errors(error_callback cb, void * u); 93 | 94 | void print_all_errors(void); 95 | 96 | void push_ssl_errors(); 97 | 98 | void push_libuv_error(int rc, const char* operation, ...); 99 | 100 | // ssl 101 | 102 | #define THUMBPRINT_HEX_LENGTH 41 // 40 chars + null terminator 103 | 104 | typedef struct server_state_init { 105 | const char* cert; 106 | const char* key; 107 | const char* address; 108 | int port; 109 | connection_handler_t* handler; 110 | char* known_thumprints[THUMBPRINT_HEX_LENGTH]; 111 | int known_thumprints_count; 112 | 113 | } server_state_init_t; 114 | 115 | server_state_t* server_state_create(server_state_init_t* options); 116 | 117 | void server_state_drop(server_state_t* s); 118 | 119 | typedef struct connection_handler { 120 | 121 | void(*failed_connection)(void); 122 | 123 | void (*connection_error)(tls_uv_connection_state_t* connection); 124 | 125 | tls_uv_connection_state_t* (*create_connection)(void); 126 | 127 | int (*connection_recv)(tls_uv_connection_state_t* connection, cmd_t* cmd); 128 | 129 | } connection_handler_t; 130 | 131 | int server_state_run(server_state_t* s); 132 | 133 | // network 134 | int connection_reply(cmd_t* c, void* buf, size_t len); 135 | 136 | int connection_reply_format(cmd_t* c, const char* format, ...); 137 | 138 | 139 | // util 140 | 141 | int vasprintf(char **strp, const char *format, va_list ap); 142 | 143 | int asprintf(char **strp, const char *format, ...); -------------------------------------------------------------------------------- /NetworkProtocol/platform.c: -------------------------------------------------------------------------------- 1 | #include "platform.h" 2 | 3 | #if !_WIN32 4 | 5 | int GetLastError() { 6 | return errno; 7 | } 8 | 9 | #else 10 | 11 | int strncasecmp(const char *s1, const char *s2, size_t size) { 12 | return _strnicmp(s1, s2, size); 13 | } 14 | 15 | 16 | int strcasecmp(const char *s1, const char *s2) { 17 | return _stricmp(s1, s2); 18 | } 19 | 20 | int close(int socket) { 21 | return closesocket(socket); 22 | } 23 | 24 | #endif 25 | 26 | 27 | int network_one_time_init() { 28 | #if _WIN32 29 | WSADATA wsaData; 30 | return WSAStartup(MAKEWORD(2, 2), &wsaData); 31 | #else 32 | return 0; 33 | #endif 34 | } 35 | -------------------------------------------------------------------------------- /NetworkProtocol/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if _WIN32 4 | #include 5 | #endif 6 | 7 | 8 | #if !_WIN32 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define INVALID_SOCKET -1 16 | 17 | int GetLastError(); 18 | 19 | #define get_server_method TLS_server_method 20 | 21 | #define strtok_s strtok_r 22 | 23 | #else 24 | 25 | #define get_server_method TLSv1_2_server_method 26 | 27 | int strcasecmp(const char *s1, const char *s2); 28 | 29 | int strncasecmp(const char *s1, const char *s2, size_t size); 30 | 31 | int close(int socket); 32 | 33 | #endif 34 | 35 | int network_one_time_init(); 36 | 37 | 38 | 39 | #ifdef _MSC_VER 40 | #define thread_local_variable _declspec(thread) 41 | #else 42 | #define thread_local_variable __thread 43 | #endif -------------------------------------------------------------------------------- /NetworkProtocol/protocol.c: -------------------------------------------------------------------------------- 1 | #include "internal.h" 2 | 3 | 4 | int connection_reply(struct cmd* c, void* buf, size_t len) { 5 | char* msg; 6 | int rc; 7 | if (c->sequence == NULL) { 8 | char* format = "Cannot reply to a message that has no Sequence header: %s"; 9 | int len = asprintf(&msg, "Cannot reply to a message that has no Sequence header: %s", c->argv[0]); 10 | if (len == -1) { 11 | msg = format; 12 | len = strlen(format); 13 | } 14 | push_error(EINVAL, msg); 15 | connection_write(c->connection ,msg, len); 16 | if (msg != format) { 17 | free(msg); 18 | } 19 | return 0; 20 | } 21 | 22 | rc = asprintf(&msg, "OK\r\nSequence: %s\r\nSize: %i\r\n\r\n", c->sequence, len); 23 | if (rc == -1) { 24 | push_error(EINVAL, "Failed to format message headers to write to connection"); 25 | return 0; 26 | } 27 | // here we assume that this is a tiny write that will be buffered 28 | // and not be sent on its own packet 29 | rc = connection_write(c->connection, msg, rc); 30 | 31 | free(msg); 32 | 33 | if (rc == 0) 34 | return rc; 35 | 36 | rc = connection_write(c->connection, buf, len); 37 | 38 | return rc; 39 | } 40 | 41 | int connection_reply_format(struct cmd* c, const char* format, ...) { 42 | va_list ap; 43 | int rc; 44 | va_start(ap, format); 45 | char * msg; 46 | int len = vasprintf(&msg, format, ap); 47 | va_end(ap); 48 | 49 | if (len == -1) { 50 | push_error(EINVAL, "Failed to format message to write to connection"); 51 | return 0; 52 | } 53 | 54 | rc = connection_reply(c, msg, len); 55 | free(msg); 56 | return rc; 57 | } 58 | 59 | 60 | static cmd_t* parse_command(tls_uv_connection_state_t* c, char* buffer, size_t len) { 61 | char* line_ctx = NULL, *ws_ctx = NULL, *line, *arg; 62 | struct cmd* cmd = NULL; 63 | char* copy = malloc(len+1); 64 | if (copy == NULL) { 65 | push_error(ENOMEM, "Unable to allocate command memroy"); 66 | goto error_cleanup; 67 | } 68 | // now we need to have our own private copy of this 69 | memcpy(copy, buffer, len); 70 | copy[len] = 0; // ensure null terminator! 71 | 72 | cmd = calloc(1, sizeof(struct cmd)); 73 | if (cmd == NULL) { 74 | push_error(ENOMEM, "Unable to allocate command memroy"); 75 | goto error_cleanup; 76 | } 77 | cmd->connection = c; 78 | cmd->cmd_buffer = copy; 79 | line = strtok_s(copy, "\r\n", &line_ctx); 80 | if (line == NULL) { 81 | push_error(EINVAL, "Unable to find \r\n in the provided buffer"); 82 | goto error_cleanup; 83 | } 84 | arg = strtok_s(line, " ", &ws_ctx); 85 | if (arg == NULL) { 86 | push_error(EINVAL, "Invalid message command line: %s", line); 87 | goto error_cleanup; 88 | } 89 | 90 | do 91 | { 92 | cmd->argc++; 93 | cmd->argv = realloc(cmd->argv, sizeof(char*) * cmd->argc); 94 | cmd->argv[cmd->argc - 1] = arg; 95 | arg = strtok_s(NULL, " ", &ws_ctx); 96 | } while (arg != NULL); 97 | 98 | while (1) 99 | { 100 | line = strtok_s(NULL, "\r\n", &line_ctx); 101 | if (line == NULL) 102 | break; 103 | arg = strtok_s(line, ":", &ws_ctx); 104 | 105 | if (arg == NULL) { 106 | push_error(EINVAL, "Header line does not contain ':' separator: %s", line); 107 | goto error_cleanup; 108 | } 109 | 110 | while (*ws_ctx != 0 && *ws_ctx == ' ') 111 | ws_ctx++; // skip initial space 112 | 113 | cmd->headers_count++; 114 | cmd->headers = realloc(cmd->headers, sizeof(struct header) *cmd->headers_count); 115 | cmd->headers[cmd->headers_count - 1].key = arg; 116 | cmd->headers[cmd->headers_count - 1].value = ws_ctx; 117 | 118 | if (strcasecmp("Sequence", arg) == 0) { 119 | cmd->sequence = ws_ctx; 120 | } 121 | } 122 | return cmd; 123 | 124 | error_cleanup: 125 | if (copy != NULL) 126 | free(copy); 127 | if (cmd != NULL) { 128 | cmd_drop(cmd); 129 | } 130 | return NULL; 131 | } 132 | 133 | void cmd_drop(struct cmd * cmd) 134 | { 135 | if (cmd->argv != NULL) 136 | free(cmd->argv); 137 | if (cmd->headers != NULL) 138 | free(cmd->headers); 139 | if (cmd->cmd_buffer != NULL) 140 | free(cmd->cmd_buffer); 141 | free(cmd); 142 | } 143 | 144 | 145 | int read_message(tls_uv_connection_state_t* c, void* buffer, int nread) { 146 | while (nread > 0) 147 | { 148 | int to_copy = MSG_SIZE - c->used_buffer; 149 | to_copy = to_copy < nread ? to_copy : nread; 150 | nread -= to_copy; 151 | memcpy(c->buffer + c->used_buffer, buffer, to_copy); 152 | c->used_buffer += to_copy; 153 | 154 | // first, need to check if we already 155 | // read the value from the network 156 | if (c->used_buffer > 0) { 157 | char* final = strnstr(c->buffer + c->to_scan, "\r\n\r\n", c->used_buffer - c->to_scan); 158 | if (final != NULL) { 159 | cmd_t* cmd = parse_command(c, c->buffer, final - c->buffer + 2/*include one \r\n*/); 160 | 161 | int rc = c->server->options.handler->connection_recv(c, cmd); 162 | 163 | // explicitly not freeing it, this should be done by the caller 164 | // cmd_drop(cmd); 165 | 166 | if (rc == 0) 167 | return 0; 168 | 169 | // now move the rest of the buffer that doesn't belong to this command 170 | // adding 4 for the length of the msg separator (\r\n\r\n) 171 | c->used_buffer -= (final + 4) - c->buffer; 172 | memmove(c->buffer, final + 4, c->used_buffer); 173 | c->to_scan = 0; 174 | continue; 175 | } 176 | c->to_scan = c->used_buffer - 3 < 0 ? 0 : c->used_buffer - 3; 177 | } 178 | if (MSG_SIZE - c->used_buffer == 0) { 179 | push_error(EINVAL, "Message size is too large, after 8KB, " 180 | "couldn't find \r\n separator, aborting connection."); 181 | return 0; 182 | } 183 | } 184 | return 1; 185 | } 186 | -------------------------------------------------------------------------------- /NetworkProtocol/tryout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "network.h" 5 | 6 | 7 | tls_uv_connection_state_t* create_connection(void){ 8 | static int counter = 0; 9 | tls_uv_connection_state_t* connection = malloc(sizeof(tls_uv_connection_state_t)); 10 | if (connection == NULL) 11 | return NULL; 12 | *(int*)(connection->user_data)= ++counter; 13 | 14 | printf("Connection created %d\n", *(int*)connection->user_data); 15 | 16 | return connection; 17 | } 18 | 19 | void on_connection_dropped(tls_uv_connection_state_t* connection) { 20 | print_all_errors(); 21 | printf("Connection dropped %d\n", *(int*)connection->user_data); 22 | } 23 | 24 | int on_connection_recv(tls_uv_connection_state_t* connection, cmd_t* cmd) { 25 | int rc; 26 | if (strcasecmp("GET", cmd->argv[0]) == 0 && cmd->argc > 1) { 27 | rc = connection_reply_format(cmd, "%s", cmd->argv[1]); 28 | } 29 | else { 30 | rc = connection_reply_format(cmd, "Unknown command: %s", cmd->argv[0]); 31 | } 32 | cmd_drop(cmd); 33 | return rc; 34 | } 35 | 36 | int main(int argc, char **argv) 37 | { 38 | int rc; 39 | int sock = -1; 40 | server_state_t* srv_state; 41 | 42 | const char* cert = "example-com.cert.pem"; 43 | const char* key = "example-com.key.pem"; 44 | 45 | connection_handler_t handler = { 46 | print_all_errors, 47 | on_connection_dropped, 48 | create_connection, 49 | on_connection_recv 50 | }; 51 | 52 | server_state_init_t options = { 53 | cert, 54 | key, 55 | "0.0.0.0", 56 | 4433, 57 | &handler, 58 | { // allowed certs 59 | "1776821DB1002B0E2A9B4EE3D5EE14133D367009" , 60 | "AE535D83572189D3EDFD1568DC76275BE33B07F5" 61 | }, 62 | 2 // number of allowed certs 63 | }; 64 | srv_state = server_state_create(&options); 65 | 66 | if (srv_state == NULL) { 67 | goto handle_error; 68 | } 69 | 70 | printf("Ready...\n"); 71 | 72 | rc = server_state_run(srv_state); // app stops here 73 | 74 | handle_error: 75 | 76 | if(srv_state != NULL) 77 | server_state_drop(srv_state); 78 | 79 | print_all_errors(); 80 | 81 | 82 | return rc; 83 | } 84 | -------------------------------------------------------------------------------- /NetworkProtocol/utils.c: -------------------------------------------------------------------------------- 1 | #include "platform.h" 2 | 3 | // taken from: 4 | // https://github.com/lattera/freebsd/blob/master/lib/libc/string/strnstr.c 5 | char * 6 | strnstr(const char *s, const char *find, size_t slen) 7 | { 8 | char c, sc; 9 | size_t len; 10 | 11 | if ((c = *find++) != '\0') { 12 | len = strlen(find); 13 | do { 14 | do { 15 | if (slen-- < 1 || (sc = *s++) == '\0') 16 | return (NULL); 17 | } while (sc != c); 18 | if (len > slen) 19 | return (NULL); 20 | } while (strncmp(s, find, len) != 0); 21 | s--; 22 | } 23 | return ((char *)s); 24 | } 25 | -------------------------------------------------------------------------------- /NetworkProtocol/vasprintf.c: -------------------------------------------------------------------------------- 1 | #include "network.h" 2 | #include 3 | 4 | #if !WIN32 5 | int _vscprintf(const char * format, va_list pargs) { 6 | int retval; 7 | va_list argcopy; 8 | va_copy(argcopy, pargs); 9 | retval = vsnprintf(NULL, 0, format, argcopy); 10 | va_end(argcopy); 11 | return retval; 12 | } 13 | #endif 14 | 15 | int asprintf(char **strp, const char *format, ...) { 16 | va_list ap; 17 | int rc; 18 | va_start(ap, format); 19 | 20 | rc = vasprintf(strp, format, ap); 21 | 22 | va_end(ap); 23 | 24 | return rc; 25 | } 26 | 27 | int vasprintf(char **strp, const char *format, va_list ap) 28 | { 29 | int len = _vscprintf(format, ap); 30 | if (len == -1) 31 | return -1; 32 | char *str = (char*)malloc((size_t)len + 1); 33 | if (!str) 34 | return -1; 35 | int retval = vsnprintf(str, len + 1, format, ap); 36 | if (retval == -1) { 37 | free(str); 38 | return -1; 39 | } 40 | *strp = str; 41 | return retval; 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generic-network-protocol 2 | 3 | This is meant as a playground to implement the network protocol described here: 4 | 5 | https://ayende.com/blog/185217-C/design-exercise-a-generic-network-protocol 6 | 7 | This is implemented in C and is intended to be an exercise in writing low level C code that does something interesting. 8 | --------------------------------------------------------------------------------