├── .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 |
--------------------------------------------------------------------------------