aboutsummaryrefslogtreecommitdiff
path: root/RATIONALE
blob: 1851aeb7829584bea55c7c2475f071b12dc06661 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
This document is a summary of the changes made to GNUnet for version
0.9.x (from 0.8.x) and what this major redesign tries to address.

First of all, the redesign does not (intentionally) change anything
fundamental about the application-level protocols or how files are
encoded and shared.  However, it is not protocol-compatible due to
other changes that do not relate to the essence of the application
protocols.  This choice was made since productive development and
readable code were considered more important than compatibility at
this point.

The redesign tries to address the following major problem groups
describing isssues that apply more or less to all GNUnet versions
prior to 0.9.x:


PROBLEM GROUP 1 (scalability):
* The code was modular, but bugs were not.  Memory corruption
  in one plugin could cause crashes in others and it was not
  always easy to identify the culprit.  This approach
  fundamentally does not scale (in the sense of GNUnet being
  a framework and a GNUnet server running hundreds of 
  different application protocols -- and the result still
  being debuggable, secure and stable).
* The code was heavily multi-threaded resulting in complex
  locking operations.  GNUnet 0.8.x had over 70 different
  mutexes and almost 1000 lines of lock/unlock operations.
  It is challenging for even good programmers to program or 
  maintain good multi-threaded code with this complexity.
  The excessive locking essentially prevents GNUnet 0.8 from
  actually doing much in parallel on multicores.
* Despite efforts like Freeway, it was virtually 
  impossible to contribute code to GNUnet 0.8 that was not
  writen in C/C++.
* Changes to the configuration almost always required restarts
  of gnunetd; the existence of change-notifications does not
  really change that (how many users are even aware of SIGHUP,
  and how few options worked with that -- and at what expense
  in code complexity!).
* Valgrinding could only be done for the entire gnunetd
  process.  Given that gnunetd does quite a bit of 
  CPU-intensive crypto, this could not be done for a system
  under heavy (or even moderate) load.
* Stack overflows with threads, while rare under Linux these
  days, result in really nasty and hard-to-find crashes.
* structs of function pointers in service APIs were
  needlessly adding complexity, especially since in 
  most cases there was no actual polymorphism

SOLUTION:
* Use multiple, lously-coupled processes and one big select
  loop in each (supported by a powerful util library to eliminate
  code duplication for each process).  
* Eliminate all threads, manage the processes with a 
  master-process (gnunet-arm, for automatic restart manager) 
  which also ensures that configuration changes trigger the 
  necessary restarts.
* Use continuations (with timeouts) as a way to unify
  cron-jobs and other event-based code (such as waiting
  on network IO).
  => Using multiple processes ensures that memory corruption
     stays localized.  
  => Using multiple processes will make it easy to contribute
     services written in other language(s). 
  => Individual services can now be subjected to valgrind
  => Process priorities can be used to schedule the CPU better
  Note that we can not just use one process with a big
  select loop because we have blocking operations (and the
  blocking is outside of our control, thanks to MySQL,
  sqlite, gethostbyaddr, etc.).  So in order to perform
  reasonably well, we need some construct for parallel
  execution.  

  RULE: If your service contains blocking functions, it
        MUST be a process by itself.  If your service
        is sufficiently complex, you MAY choose to make
        it a separate process.
* Eliminate structs with function pointers for service APIs;
  instead, provide a library (still ending in _service.h) API
  that transmits the requests nicely to the respective
  process (easier to use, no need to "request" service
  in the first place; API can cause process to be started/stopped
  via ARM if necessary).


PROBLEM GROUP 2 (UTIL-APIs causing bugs):
* The existing logging functions were awkward to use and
  their expressive power was never really used for much.
* While we had some rules for naming functions, there
  were still plenty of inconsistencies.
* Specification of default values in configuration could 
  result in inconsistencies between defaults in
  config.scm and defaults used by the program; also,
  different defaults might have been specified for the
  same option in different parts of the program.
* The TIME API did not distinguish between absolute
  and relative time, requiring users to know which
  type of value some variable contained and to
  manually convert properly.  Combined with the
  possibility of integer overflows this is a major
  source of bugs.
* The TIME API for seconds has a theoretical problem
  with a 32-bit overflow on some platforms which is
  only partially fixed by the old code with some
  hackery.

SOLUTION:
* Logging was radically simplified.
* Functions are now more conistently named.
* Configuration has no more defaults; instead,
  we load a global default configuration file
  before the user-specific configuration (which 
  can be used to override defaults); the global
  default configuration file will be generated 
  from config.scm.
* Time now distinguishes between
  struct GNUNET_TIME_Absolute and
  struct GNUNET_TIME_Relative.  We use structs
  so that the compiler won't coerce for us 
  (forcing the use of specific conversion
  functions which have checks for overflows, etc.).
  Naturally the need to use these functions makes
  the code a bit more verbose, but that's a good
  thing given the potential for bugs.
* There is no more TIME API function to do anything
  with 32-bit seconds
* There is now a bandwidth API to handle 
  non-trivial bandwidth utilization calculations


PROBLEM GROUP 3 (statistics):
* Databases and others needed to store capacity values
  similar to what stats was already doing, but
  across process lifetimes ("state"-API was a partial
  solution for that, but using it was clunky)
* Only gnunetd could use statistics, but other
  processes in the GNUnet system might have had
  good uses for it as well

SOLUTION:
* New statistics library and service that offer
  an API to inspect and modify statistics
* Statistics are distinguished by service name
  in addition to the name of the value
* Statistics can be marked as persistent, in
  which case they are written to disk when
  the statistics service shuts down.
  => One solution for existing stats uses,
     application stats, database stats and
     versioning information!


PROBLEM GROUP 4 (Testing):
* The existing structure of the code with modules
  stored in places far away from the test code
  resulted in tools like lcov not giving good results.
* The codebase had evolved into a complex, deeply
  nested hierarchy often with directories that
  then only contained a single file.  Some of these
  files had the same name making it hard to find
  the source corresponding to a crash based on 
  the reported filename/line information.
* Non-trivial portions of the code lacked good testcases,
  and it was not always obvious which parts of the code 
  were not well-tested.

SOLUTION:
* Code that should be tested together is now
  in the same directory.
* The hierarchy is now essentially flat, each
  major service having on directory under src/;
  naming conventions help to make sure that
  files have globally-unique names
* All code added to the new repository must
  come with testcases with reasonable coverage.


PROBLEM GROUP 5 (core/transports):
* The new DV service requires session key exchange
  between DV-neighbours, but the existing
  session key code can not be used to achieve this.
* The core requires certain services
  (such as identity, pingpong, fragmentation,
   transport, traffic, session) which makes it 
  meaningless to have these as modules
  (especially since there is really only one
  way to implement these)
* HELLO's are larger than necessary since we need
  one for each transport (and hence often have
  to pick a subset of our HELLOs to transmit)
* Fragmentation is done at the core level but only
  required for a few transports; future versions of
  these transports might want to be aware of fragments
  and do things like retransmission
* Autoconfiguration is hard since we have no good
  way to detect (and then use securely) our external IP address
* It is currently not possible for multiple transports
  between the same pair of peers to be used concurrently
  in the same direction(s)
* We're using lots of cron-based jobs to periodically
  try (and fail) to build and transmit

SOLUTION:
* Rewrite core to integrate most of these services
  into one "core" service.
* Redesign HELLO to contain the addresses for
  all enabled transports in one message (avoiding
  having to transmit the public key and signature
  many, many times)
* With discovery being part of the transport service,
  it is now also possible to "learn" our external
  IP address from other peers (we just add plausible
  addresses to the list; other peers will discard 
  those addresses that don't work for them!)
* New DV will consist of a "transport" and a 
  high-level service (to handle encrypted DV
  control- and data-messages).
* Move expiration from one field per HELLO to one
  per address
* Require signature in PONG, not in HELLO (and confirm
  on address at a time)
* Move fragmentation into helper library linked
  against by UDP (and others that might need it)
* Link-to-link advertising of our HELLO is transport
  responsibility; global advertising/bootstrap remains
  responsibility of higher layers
* Change APIs to be event-based (transports pull for
  transmission data instead of core pushing and failing)


PROBLEM GROUP 6 (FS-APIs):
* As with gnunetd, the FS-APIs are heavily threaded,
  resulting in hard-to-understand code (slightly
  better than gnunetd, but not much).
* GTK in particular does not like this, resulting 
  in complicated code to switch to the GTK event
  thread when needed (which may still be causing
  problems on Gnome, not sure).
* If GUIs die (or are not properly shutdown), state
  of current transactions is lost (FSUI only
  saves to disk on shutdown)
* FILENAME metadata is killed by ECRS/FSUI to avoid
  exposing HOME, but what if the user set it manually?
* The DHT was a generic data structure with no
  support for ECRS-style block validation

SOLUTION:
* Eliminate threads from FS-APIs
* Incrementally store FS-state always also on disk using many
  small files instead of one big file
* Have API to manipulate sharing tree before
  upload; have auto-construction modify FILENAME
  but allow user-modifications afterwards
* DHT API was extended with a BLOCK API for content
  validation by block type; validators for FS and
  DHT block types were written; BLOCK API is also
  used by gap routing code.


PROBLEM GROUP 7 (User experience):
* Searches often do not return a sufficient / significant number of
  results
* Sharing a directory with thousands of similar files (image/jpeg)
  creates thousands of search results for the mime-type keyword
  (problem with DB performance, network transmission, caching,
   end-user display, etc.)
* Users that wanted to share important content had no way to
  tell the system to replicate it more; replication was also
  inefficient (this desired feature was sometimes called
  "power" publishing or content pushing)

SOLUTION:
* Have option to canonicalize keywords (see suggestion on mailinglist end of
  June 2009: keep consonants and sort those alphabetically); not
  fully implemented yet
* When sharing directories, extract keywords first and then
  push keywords that are common in all files up to the
  directory level; when processing an AND-ed query and a directory
  is found to match the result, do an inspection on the metadata
  of the files in the directory to possibly produce further results
  (requires downloading of the directory in the background);
  needs more testing
* A desired replication level can now be specified and is tracked
  in the datastore; migration prefers content with a high
  replication level (which decreases as replicase are created)
  => datastore format changed; we also took out a size field 
     that was redundant, so the overall overhead remains the same
* Peers with a full disk (or disabled migration) can now notify 
  other peers that they are not interested in migration right
  now; as a result, less bandwidth is wasted pushing content
  to these peers (and replication counters are not generally
  decreased based on copies that are just discarded; naturally,
  there is still no guarantee that the replicas will stay
  available) 



SUMMARY:
* Features eliminated from util:
  - threading (goal: good riddance!)
  - complex logging features [ectx-passing, target-kinds] (goal: good riddance!)
  - complex configuration features [defaults, notifications] (goal: good riddance!)
  - network traffic monitors (goal: eliminate)
  - IPC semaphores (goal: d-bus? / eliminate?)
  - second timers
* New features in util:
  - scheduler
  - service and program boot-strap code
  - bandwidth and time APIs
  - buffered IO API
  - HKDF implementation (crypto)
  - load calculation API
  - bandwidth calculation API
* Major changes in util:
  - more expressive server (replaces selector)
  - DNS lookup replaced by async service