├── .gitignore
├── LICENSE
├── README.md
├── build-release.sh
├── coap.go
├── common
└── log.go
├── docker
├── Dockerfile
└── start.sh
├── go.mod
├── go.sum
├── http.go
├── jaeger.go
├── main.go
├── maps
├── common_keys.json
├── common_values.json
├── edu_types.json
├── error_codes.json
├── event_types.json
├── extra_flate_data
├── query_params.json
└── routes.json
├── net.go
├── openconn.go
├── routing.go
├── substitution.go
├── types
├── cbor.go
├── compressor.go
├── json.go
└── keystore.go
└── utils.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dev files
2 | .idea/
3 |
4 | # Built binary
5 | coap-proxy
6 | bin/
7 |
8 | # Temporary test directory
9 | _tmp/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CoAP <-> HTTP proxy
2 |
3 | ## Introduction
4 |
5 | coap-proxy is a **proof of concept experiment** for converting normal Matrix HTTPS+JSON
6 | traffic into an ultra-low-bandwidth CoAP+CBOR+Flate+Noise+UDP network transport.
7 | The resulting transport typically uses 35x-70x less bandwidth than HTTPS+JSON, and
8 | attempts to fit typical Matrix transactions into a single roundtrip on a 100bps network link.
9 |
10 | coap-proxy depends heavily on our experimental fork of [go-ocf/go-coap](https://github.com/matrix-org/go-coap),
11 | which implements Noise-based encryption, compression hooks and retry semantics.
12 |
13 | More details on the transport can be found in our
14 | "Breaking the 100 bits per second barrier with Matrix" FOSDEM 2019 talk:
15 | https://matrix.org/blog/2019/03/12/breaking-the-100bps-barrier-with-matrix-meshsim-coap-proxy/
16 |
17 | The typical way to run coap-proxy is via docker using the [meshsim](https://github.com/matrix-org/meshsim)
18 | network simulator, which fires up docker containers in a simulated bad network environment
19 | containing both synapse and coap-proxy configured such that coap-proxy intercepts both client-server and
20 | server-server traffic.
21 |
22 | ## Limitations
23 |
24 | As a proof-of-concept, coap-proxy currently has some major limitations:
25 |
26 | * Encryption using [Noise](https://noiseprotocol.org) is highly experimental:
27 | * The transport layer security here is entirely custom and hand-wrapped
28 | (to optimise for bandwidth at any cost), and **should not yet be trusted
29 | as secure or production ready**, nor has it been audited or reviewed or fully tested.
30 | * We use custom CoAP message codes (250, 251 and 252) to describe Noise
31 | handshake payloads - see
32 | https://github.com/matrix-org/go-coap/blob/b6887beb140cb1cd287e2cc7c4307ebe2263db90/conn.go#L360-L370.
33 | Instead we should probably be using CoAP's OSCORE rather than creating our own thing;
34 | see https://tools.ietf.org/id/draft-mattsson-lwig-security-protocol-comparison-00.html
35 | * Security is trust-on-first-use (although theoretically could support pre-shared static certificates)
36 | * IK handshakes are currently not being used due to them making go-coap's handshake state machine unreliable,
37 | which needs to be investigated in the future
38 | * coap-proxy currently assumes it runs under a trusted private network (i.e. not the internet). This means:
39 | * Signatures and checks are removed from the Matrix S2S API to save bandwidth (given the network is assumed trustworthy)
40 | * Minimal bandwidth depends on picking predictable compact hostnames which compress easily
41 | (e.g. synapse1, synapse2, synapse3...)
42 | * No work has yet been done on congestion control; the CoAP stack uses simple exponential backoff to retry connections.
43 | * We currently compress data using pre-shared static deflate compression maps.
44 | All nodes have to share precisely the same map files.
45 | * Ideally we should support streaming compression and dynamic maps.
46 | * CoAP doesn't support querystrings longer than 255 bytes; the proxy needs to spot these and work
47 | around the limit (but doesn't yet). In practice these are quite rare however.
48 | * Multiple overlapping blockwise CoAP requests to the same endpoint may get entangled, and may
49 | require application-layer mitigation. This is a design flaw in CoAP (see RFC7959 §2.4).
50 | * Encryption re-handshakes after network interruptions do not yet work.
51 | * After a sync timeout, the session cache is not updated with the source port of the new UDP flow,
52 | and so will try to send responses to the old source port.
53 | * No IPv6 support.
54 | * access_tokens currently suck up bandwidth on every request. We should swap them out for PSKs or similar.
55 |
56 | Development of coap-proxy is dependent on commercial interest - please contact
57 | `support at vector.im` if you're interested in a production grade coap-proxy!
58 |
59 | ## Build
60 |
61 | This uses the new `go mod`. Build with `go build`.
62 |
63 | ## Run
64 |
65 | Once built, the proxy's binary is located in `bin/coap-proxy`. You can give it
66 | the following flags:
67 |
68 | * `--only-coap`: Only proxy CoAP requests to HTTP and not the other way around.
69 | * `--only-http`: Only proxy HTTP requests to CoAP and not the other way around.
70 | * `--debug-log`: Print debugging logs. Coupling it with setting the environment
71 | variable `PROXY_DUMP_PAYLOADS` to any value will also dump requests and
72 | responses payloads along with their transformations during the process.
73 | * `--maps-dir DIR`: Tell the proxy to look for map files in `DIR`. Defaults to
74 | `./maps`.
75 | * `--coap-target`: Tell the proxy where to send CoAP requests.
76 |
77 | If no flag is provided, the proxy will
78 | listen for both HTTP and CoAP requests on port:
79 |
80 | * `8888` for HTTP
81 | * `5683` for CoAP
82 |
83 | ## How it works
84 |
85 | The proxy works as follows:
86 |
87 | ```
88 | --------------- ---------------- ---------------- ---------------
89 | | HTTP client | <--------> | HTTP to CoAP | <--------> | CoAP to HTTP | <--------> | HTTP server |
90 | | | HTTP | proxy | CoAP + | proxy | HTTP | |
91 | --------------- ---------------- CBOR ---------------- ---------------
92 |
93 | |__________________________________________|
94 | |
95 | coap-proxy
96 | ```
97 |
98 | ## Run the proxy for meshsim
99 |
100 | * Build the proxy
101 | * Run it by telling it to talk to the HS's proxy:
102 |
103 | ```bash
104 | ./bin/coap-proxy --coap-target localhost:20001 # Ports are 20000 + hsid
105 | ```
106 |
107 | * Make clients talk to http://localhost:8888
108 | * => profit
109 |
110 | ## Connections
111 |
112 | Whenever it's possible, the proxy will try to reuse existing connections
113 | (connect()ed UDP sockets) to specific destinations when sending CoAP requests.
114 |
115 | The only reasons that can lead it to (re)create a connection to a given
116 | destination are:
117 |
118 | * no connection to that destination exist.
119 | * writing to the connection returned raised an error (in which case we
120 | recreate the connection and retry sending the message).
121 | * the time delta between now and when the latest message written to the
122 | connection was sent is higher than the 180s sync timeout.
123 |
124 | The reason why we currently re-handshake after the 180s sync timeout
125 | is:
126 |
127 | 1. the server (and client) track sessions in networksession, which keys them
128 | based on a given {srcport, dstport} tuple.
129 | 2. the client, having connect()ed its socket, will not change src port.
130 | 3. the server always has the same dest port (5683).
131 | 4. the traffic may be NATed however, which means that after a ~180s gap in
132 | traffic, the srcport that the server sees may change, making the server
133 | see it as a new session (unless we do something to tie sessions to CoAP
134 | IDs rather than srcport/dstport tuples)
135 | 5. therefore the behaviour here probably *is* right (for now) and we
136 | should handshake after 180s of any pause of traffic.
137 |
138 | ## License
139 |
140 | Copyright 2019 New Vector Ltd
141 |
142 | This file is part of coap-proxy.
143 |
144 | coap-proxy is free software: you can redistribute it and/or modify
145 | it under the terms of the GNU General Public License as published by
146 | the Free Software Foundation, either version 3 of the License, or
147 | (at your option) any later version.
148 |
149 | coap-proxy is distributed in the hope that it will be useful,
150 | but WITHOUT ANY WARRANTY; without even the implied warranty of
151 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
152 | GNU General Public License for more details.
153 |
154 | You should have received a copy of the GNU General Public License
155 | along with coap-proxy. If not, see .
156 |
--------------------------------------------------------------------------------
/build-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for e in `gb info`; do
4 | e=`echo $e | sed 's/"//g'`
5 | export $e
6 | done
7 |
8 | mkdir $GB_PROJECT_DIR/bin 2> /dev/null
9 |
10 | set -e
11 |
12 | for project in `ls $GB_PROJECT_DIR/src`; do
13 | echo "Building $project"
14 | GOBIN="$GB_PROJECT_DIR/bin" GOPATH="$GB_PROJECT_DIR:$GB_PROJECT_DIR/vendor" go install -gcflags=-trimpath=$GB_PROJECT_DIR -asmflags=-trimpath=$GB_PROJECT_DIR src/$project/main.go
15 | strip bin/main
16 | mv bin/main bin/$project
17 | done
18 |
19 |
--------------------------------------------------------------------------------
/coap.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "errors"
7 | "log"
8 | "strings"
9 | "time"
10 |
11 | "github.com/matrix-org/coap-proxy/common"
12 | "github.com/matrix-org/coap-proxy/types"
13 |
14 | "github.com/matrix-org/go-coap"
15 | "github.com/opentracing/opentracing-go"
16 | "github.com/opentracing/opentracing-go/ext"
17 | olog "github.com/opentracing/opentracing-go/log"
18 | )
19 |
20 | var (
21 | // In-memory store for crypto keys which implements the go-coap.KeyStore
22 | // interface.
23 | keyStore = types.NewKeyStore()
24 | // Instance of go-coap.RetriesQueue we'll give to our server and clients so
25 | // they can handle retries. This needs to be dont that way and at the
26 | // application layer since the server needs to match responses to requests
27 | // the client sends.
28 | // 40 seconds as an initial delay should be enough to prevent sync responses
29 | // from being sent twice (since Riot's timeout for syncs is 30s).
30 | retriesQueue = coap.NewRetriesQueue(40*time.Second, 1)
31 | )
32 |
33 | // ServeCOAP is a function that listens for CoAP requests and responds accordingly.
34 | // It:
35 | // * Takes in a CoAP request
36 | // * Decompresses and CBOR decodes the payload if there is one
37 | // * Decompresses the request path and query parameters
38 | // * Creates an HTTP request with carried over and decompressed headers, path, body etc.
39 | // * Sends the HTTP request to an attached Homeserver, retrieves the response
40 | // * Compresses the response
41 | // * Returns it over CoAP to the requester
42 | func ServeCOAP(w coap.ResponseWriter, req *coap.Request) {
43 | ctx := context.Background()
44 |
45 | m := req.Msg
46 |
47 | if !m.IsConfirmable() {
48 | log.Printf("Got unconfirmable message")
49 | return
50 | }
51 |
52 | pl := m.Payload()
53 |
54 | var serverSpan opentracing.Span
55 | var body interface{}
56 | if len(pl) > 0 {
57 | // Decompress and decode the payload body if it exists
58 | var err error
59 | if pl, err = compressor.DecompressPayload(pl); err != nil {
60 | handleErr(err, serverSpan)
61 | return
62 | }
63 | body = cbor.Decode(pl)
64 |
65 | var carrier interface{}
66 | if bodyMap, ok := body.(map[interface{}]interface{}); ok {
67 | carrier = bodyMap["XJG"]
68 | delete(bodyMap, "XJG")
69 | }
70 |
71 | if carrierBytes, ok := carrier.([]byte); ok {
72 | common.Debugf("XFG header received len %d", len(carrierBytes))
73 | wireContext, err := opentracing.GlobalTracer().Extract(
74 | opentracing.Binary,
75 | bytes.NewReader(carrierBytes),
76 | )
77 |
78 | if err != nil {
79 | common.Debugf("Failed to extract wire context %v", err)
80 | }
81 |
82 | serverSpan = opentracing.StartSpan(
83 | "coap-server",
84 | ext.RPCServerOption(wireContext),
85 | )
86 |
87 | defer serverSpan.Finish()
88 |
89 | ctx = opentracing.ContextWithSpan(ctx, serverSpan)
90 | }
91 | }
92 |
93 | if serverSpan == nil {
94 | serverSpan, ctx = opentracing.StartSpanFromContext(ctx, "coap-server")
95 | defer serverSpan.Finish()
96 |
97 | ext.SpanKindRPCServer.Set(serverSpan)
98 | }
99 |
100 | path := m.PathString()
101 |
102 | common.Debugf("CoAP - %X: Got request on path %s", req.Msg.Token(), path)
103 |
104 | var query string
105 | s := strings.Split(path, "?")
106 | if len(s) > 1 {
107 | query = s[1]
108 | path = s[0]
109 | } else {
110 | query = ""
111 | }
112 |
113 | // Get routeID and path arguments from request path
114 | args, trailingSlash, routeID, err := argsAndRouteFromPath(path)
115 |
116 | // Get decompressed path and query parameters from the request
117 | var method, routeName string
118 | if err == nil {
119 | r := routes[routeID]
120 |
121 | common.Debugf(
122 | "CoAP - %X: Got request on route #%d (%s %s)\n",
123 | req.Msg.Token(), routeID, strings.ToUpper(r.Method), r.Path,
124 | )
125 |
126 | path, err = genExpandedPath(path, args, query, trailingSlash, routeID)
127 | if err != nil {
128 | handleErr(err, serverSpan)
129 | return
130 | }
131 |
132 | common.Debugf("CoAP - %X: Got request on URL %s", req.Msg.Token(), path)
133 |
134 | method = r.Method
135 | routeName = r.Name
136 | } else {
137 | c := req.Msg.Code()
138 | if c >= coap.GET && c <= coap.DELETE {
139 | method = c.String()
140 | if path[:1] != "/" {
141 | path = "/" + path
142 | }
143 |
144 | path, err = genExpandedPath(path, args, query, trailingSlash, -1)
145 | if err != nil {
146 | handleErr(err, serverSpan)
147 | return
148 | }
149 | } else {
150 | err = errors.New("Wrong method code: " + c.String())
151 | handleErr(err, serverSpan)
152 | return
153 | }
154 |
155 | common.Debugf(
156 | "CoAP - %X: Got request on unknown route %s %s",
157 | req.Msg.Token(), method, path,
158 | )
159 | }
160 |
161 | common.Debugf("CoAP - %X: Sending HTTP request", req.Msg.Token())
162 | common.Debug("routeName", routeName)
163 | common.Debugf("COAP options %v", req.Msg.AllOptions())
164 |
165 | // Encode the CBOR-decoded body into JSON
166 | if len(pl) > 0 {
167 | if routeName == "send_transaction" {
168 | body = compressor.DecompressTransaction(body)
169 | }
170 | pl = json.Encode(body)
171 | }
172 |
173 | // XXX: HACK: Use the coap library's MaxAge option to store origin (due to the
174 | // lib ignoring unknown options). Do not rely on this to remain functional across
175 | // lib upgrades.
176 | originOpt := m.Option(coap.LocationPath)
177 | common.Debugf("opt: %v", originOpt)
178 |
179 | // TODO: Handle fs-specific stuff
180 | var origin string
181 | if s, ok := originOpt.(string); ok {
182 | origin = s
183 | }
184 |
185 | ext.PeerHostname.Set(serverSpan, origin)
186 |
187 | // Send an HTTP request to a homeserver and receive a response
188 | pl, statusCode, err := sendHTTPRequest(
189 | ctx,
190 | method,
191 | path,
192 | pl,
193 | origin,
194 | )
195 | if err != nil {
196 | handleErr(err, serverSpan)
197 | return
198 | }
199 |
200 | common.Debugf("CoAP - %X: Got status %d", req.Msg.Token(), statusCode)
201 | common.Debugf("CoAP - %X: Sending response", req.Msg.Token())
202 |
203 | // Convert the receive HTTP status code to a CoAP one and add to response
204 | w.SetCode(statusHTTPToCoAP(statusCode))
205 |
206 | // Re-encode the JSON body into CBOR and write out
207 | if len(pl) > 0 {
208 | pl = cbor.Encode(json.Decode(pl))
209 |
210 | if pl, err = compressor.CompressPayload(pl); err != nil {
211 | handleErr(err, serverSpan)
212 | return
213 | }
214 |
215 | w.SetContentFormat(coap.AppOctets)
216 |
217 | if _, err = w.Write(pl); err != nil {
218 | handleErr(err, serverSpan)
219 | return
220 | }
221 | }
222 | }
223 |
224 | // sendCoAPRequest is a function that sends a CoAP request to another instance
225 | // of the CoAP proxy.
226 | func sendCoAPRequest(
227 | ctx context.Context, method, host, path string, routeName string, body interface{},
228 | origin *string,
229 | ) (payload []byte, statusCode coap.COAPCode, err error) {
230 | var c *openConn
231 | var exists bool
232 |
233 | // Setup OpenTracing
234 | var clientSpan opentracing.Span
235 | clientSpan, ctx = opentracing.StartSpanFromContext(ctx, "coap-client")
236 | defer clientSpan.Finish()
237 |
238 | ext.SpanKindRPCClient.Set(clientSpan)
239 |
240 | clientSpan.SetTag("coap.path", path)
241 | clientSpan.SetTag("coap.method", method)
242 |
243 | // Send to request's host unless the target has been forced
244 | var target string
245 | if len(*coapTarget) > 0 {
246 | target = *coapTarget
247 | } else {
248 | target = host + ":" + *coapPort
249 | }
250 |
251 | common.Debugf("Proxying request to %s", target)
252 |
253 | // if c, err = dialTimeout("udp", target, 300*time.Second); err != nil {
254 | // ext.Error.Set(clientSpan, true)
255 | // clientSpan.LogFields(olog.Error(err))
256 | // return
257 | // }
258 | //
259 | // defer c.Close()
260 |
261 | // If there is an existing connection, use it, otherwise provision a new one
262 | if c, exists = conns[target]; !exists || (c != nil && c.dead) {
263 | common.Debugf("No usable connection to %s, initiating a new one", target)
264 | if c, err = resetConn(target); err != nil {
265 | return
266 | }
267 | // } else if time.Now().Add(-180 * time.Second).After(c.lastMsg) {
268 | // // Reset an existing connection if the latest message sent is older than
269 | // // go-coap's syncTimeout.
270 | // if c, err = resetConn(target); err != nil {
271 | // return
272 | // }
273 | } else if exists {
274 | common.Debugf("Reusing existing connection to %s", target)
275 | }
276 |
277 | // Record the destination in the trace
278 | hostAddr := strings.Split(target, ":")[0]
279 | ext.PeerHostname.Set(clientSpan, hostAddr)
280 | ext.PeerAddress.Set(clientSpan, target)
281 |
282 | if useJaeger {
283 | carrier := &bytes.Buffer{}
284 | _ = opentracing.GlobalTracer().Inject(
285 | clientSpan.Context(),
286 | opentracing.Binary,
287 | carrier,
288 | )
289 |
290 | // Add a tracing header to the request
291 | if bodyMap, ok := body.(map[interface{}]interface{}); ok {
292 | carrierBytes := carrier.Bytes()
293 |
294 | common.Debugf("XFG header len: %d", len(carrierBytes))
295 |
296 | clientSpan.LogFields(olog.Int("jaeger-bytes", len(carrierBytes)))
297 | bodyMap["XJG"] = carrierBytes
298 | }
299 |
300 | }
301 |
302 | // Map for translating HTTP method codes to CoAP
303 | methodCodes := map[string]coap.COAPCode{
304 | "GET": coap.GET,
305 | "POST": coap.POST,
306 | "PUT": coap.PUT,
307 | "DELETE": coap.DELETE,
308 | }
309 |
310 | var bodyBytes []byte
311 | if body != nil {
312 | // Compress transaction if this a federation transaction request
313 | if routeName == "send_transaction" {
314 | body = compressor.CompressTransaction(body)
315 | common.DumpPayload("Encoded transaction", body)
316 | }
317 |
318 | // Encode body as CBOR
319 | bodyBytes = cbor.Encode(body)
320 |
321 | common.DumpPayload("Encoded body", bodyBytes)
322 |
323 | // Compress body
324 | if bodyBytes, err = compressor.CompressPayload(bodyBytes); err != nil {
325 | ext.Error.Set(clientSpan, true)
326 | clientSpan.LogFields(olog.Error(err))
327 | return
328 | }
329 | }
330 |
331 | common.Debugf("Sending %d bytes in compressed payload", len(bodyBytes))
332 | clientSpan.LogFields(olog.Int("payload-bytes", len(bodyBytes)))
333 |
334 | // Create a new CoAP request
335 | req := c.NewMessage(coap.MessageParams{
336 | Type: coap.Confirmable,
337 | Code: methodCodes[strings.ToUpper(method)],
338 | MessageID: uint16(r1.Intn(100000)),
339 | Token: randSlice(2),
340 | Payload: bodyBytes,
341 | })
342 |
343 | if origin != nil {
344 | // XXX: HACK: Use the coap library's MaxAge option to store origin (due to the
345 | // lib ignoring unknown options). Do not rely on this to remain functional across
346 | // lib upgrades.
347 | req.SetOption(coap.LocationPath, *origin)
348 | }
349 |
350 | // This option should be set last, to aid compression of the packet
351 | req.SetOption(coap.ContentFormat, coap.AppOctets)
352 |
353 | if len(path) > 250 {
354 | // We can't send long paths, so lets bail out here
355 | err = errors.New("Path too long: " + path)
356 | return
357 | }
358 |
359 | req.SetPathString(path)
360 |
361 | log.Printf("HTTP: Sending CoAP request with token %X (path: %v)", req.Token(), path)
362 |
363 | // Send the CoAP request and receive a response
364 | common.Debugf("opts %v", req.AllOptions())
365 | res, err := c.Exchange(req)
366 |
367 | // Check for errors
368 | if err != nil {
369 | log.Printf("Closing CoAP connection because of error: %v", err)
370 |
371 | if c, err = resetConn(target); err != nil {
372 | return
373 | }
374 |
375 | if res, err = c.Exchange(req); err != nil {
376 | ext.Error.Set(clientSpan, true)
377 | clientSpan.LogFields(olog.Error(err))
378 | log.Printf("HTTP failed to exchange coap: %v", err)
379 | return
380 | }
381 | }
382 |
383 | // Receive and decompress the response payload
384 | rawPayload := res.Payload()
385 | clientSpan.LogFields(olog.Int("response-payload-bytes", len(rawPayload)))
386 |
387 | common.Debugf("HTTP: Got response to CoAP request %X with %d bytes in response payload", res.Token(), len(rawPayload))
388 |
389 | pl, err := compressor.DecompressPayload(rawPayload)
390 | // common.Debugf("Got %d bytes in response payload (%d decompressed)", len(rawPayload), len(pl))
391 |
392 | // Keep track of the last successfully received message for connection timeout purposes
393 | c.lastMsg = time.Now()
394 |
395 | return pl, res.Code(), err
396 | }
397 |
--------------------------------------------------------------------------------
/common/log.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 New Vector Ltd
2 | //
3 | // This file is part of coap-proxy.
4 | //
5 | // coap-proxy is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // coap-proxy is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with coap-proxy. If not, see .
17 |
18 | package common
19 |
20 | import (
21 | "log"
22 | "os"
23 | )
24 |
25 | var (
26 | debugLogEnabled, dumpPayloads bool
27 | )
28 |
29 | // EnableDebugLogging enables debug logging.
30 | func EnableDebugLogging() {
31 | debugLogEnabled = true
32 | _, dumpPayloads = os.LookupEnv("PROXY_DUMP_PAYLOADS")
33 | }
34 |
35 | // Debug prints a debug log with the given parameters if debug logging is
36 | // enabled.
37 | func Debug(msg ...interface{}) {
38 | if debugLogEnabled {
39 | log.Println("DEBUG:", msg)
40 | }
41 | }
42 |
43 | // Debugf prints a debug log with the given parameters and format if debug
44 | // logging is enabled.
45 | func Debugf(format string, args ...interface{}) {
46 | if debugLogEnabled {
47 | format = "DEBUG: " + format
48 | log.Printf(format, args...)
49 | }
50 | }
51 |
52 | // DumpPayload dumps a payload if both debug logging is enabled and the
53 | // PROXY_DUMP_PAYLOADS environment variable is set.
54 | func DumpPayload(label string, pl interface{}) {
55 | if dumpPayloads {
56 | Debugf("%s: %v", label, pl)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/golang:1.12.4-stretch
2 |
3 | COPY . /proxy
4 | WORKDIR /proxy
5 |
6 | RUN go build
7 |
8 | EXPOSE 8888/tcp 5683/udp
9 |
10 | COPY ./docker/start.sh /start.sh
11 |
12 | ENTRYPOINT ["/start.sh"]
13 |
--------------------------------------------------------------------------------
/docker/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | base_cmd="/proxy/coap-proxy -maps-dir /proxy/maps -debug-log -http-target http://synapse:8008"
4 |
5 | if [ -z "$COAP_ENABLE_ENCRYPTION" ]; then
6 | base_cmd="$base_cmd -disable-encryption"
7 | fi
8 |
9 | if [ -n "$PROXY_COAP_TARGET" ]; then
10 | $base_cmd --coap-target $PROXY_COAP_TARGET
11 | else
12 | $base_cmd
13 | fi
14 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/matrix-org/coap-proxy
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065
7 | github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6
8 | github.com/matrix-org/go-coap v0.0.0-20190605161234-2b2386eef86a
9 | github.com/opentracing/opentracing-go v1.1.0
10 | github.com/pkg/errors v0.8.1 // indirect
11 | github.com/uber/jaeger-client-go v2.16.0+incompatible
12 | github.com/uber/jaeger-lib v2.0.0+incompatible
13 | github.com/ugorji/go v1.1.4
14 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect
15 | golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065 h1:7QVNyw2v9R1qOvbe9vfeVJWWKCSnd2Ap+8l8/CtG9LM=
2 | github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065/go.mod h1:uN4GbWHfit2ByfOKQ4K6fuLy1/Os2eLynsIrDvjiDgM=
3 | github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as=
4 | github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=
5 | github.com/matrix-org/go-coap v0.0.0-20190530164141-41ffa5653fe1 h1:e40KJpkb0CAI3MWXtmqhYyC9bG7pCCyoOtFNkI++MUo=
6 | github.com/matrix-org/go-coap v0.0.0-20190530164141-41ffa5653fe1/go.mod h1:5RXA2kXFkk9NKcVfdVXqzKpQ7HPpvZWLrE/hP/PE8Ao=
7 | github.com/matrix-org/go-coap v0.0.0-20190605161234-2b2386eef86a h1:VaziGp6D3lTT8Q7faqFed4IyohlO+ixcpmTH5y8FKRU=
8 | github.com/matrix-org/go-coap v0.0.0-20190605161234-2b2386eef86a/go.mod h1:5RXA2kXFkk9NKcVfdVXqzKpQ7HPpvZWLrE/hP/PE8Ao=
9 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
10 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
11 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
12 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
13 | github.com/uber/jaeger-client-go v2.16.0+incompatible h1:Q2Pp6v3QYiocMxomCaJuwQGFt7E53bPYqEgug/AoBtY=
14 | github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
15 | github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw=
16 | github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
17 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
18 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
20 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
21 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
22 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
23 | golang.org/x/net v0.0.0-20190514140710-3ec191127204 h1:4yG6GqBtw9C+UrLp6s2wtSniayy/Vd/3F7ffLE427XI=
24 | golang.org/x/net v0.0.0-20190514140710-3ec191127204/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
26 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
27 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
28 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
29 |
--------------------------------------------------------------------------------
/http.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "strings"
11 |
12 | "github.com/matrix-org/coap-proxy/common"
13 |
14 | "github.com/opentracing/opentracing-go"
15 | "github.com/opentracing/opentracing-go/ext"
16 | olog "github.com/opentracing/opentracing-go/log"
17 | )
18 |
19 | // Client for outbound HTTP requests to homeservers
20 | var httpClient = &http.Client{}
21 |
22 | // handler is a struct that acts as an http.Handler, where its ServeHTTP method is used to handle HTTP requests
23 | type handler struct{}
24 |
25 | // ServeHTTP is a function implemented on handler which handles HTTP requests.
26 | // It:
27 | // * Takes in an HTTP request
28 | // * Compresses the path, query parameters and body if possible
29 | // * Creates a CoAP request with carried over and compressed headers, path, body etc.
30 | // * Sends the CoAP request to another proxy, retrieves the response
31 | // * Decompresses the response back into normal HTTP
32 | // * Returns it to the original sender
33 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
34 | if r.Method == "OPTIONS" {
35 | common.Debug("Got preflight request")
36 | w.Header().Set("Access-Control-Allow-Origin", "*")
37 | w.Header().Set("Access-Control-Allow-Headers", "content-type,authorization")
38 | w.Header().Set("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS")
39 | return
40 | }
41 |
42 | common.Debugf("HTTP: Got request on path %s", r.URL.Path)
43 |
44 | // Set up an OpenTracing span to track this request's lifecycle
45 | var serverSpan opentracing.Span
46 | wireContext, err := opentracing.GlobalTracer().Extract(
47 | opentracing.HTTPHeaders,
48 | opentracing.HTTPHeadersCarrier(r.Header))
49 |
50 | serverSpan = opentracing.StartSpan(
51 | "http_server",
52 | ext.RPCServerOption(wireContext))
53 |
54 | defer serverSpan.Finish()
55 |
56 | ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
57 |
58 | ext.HTTPMethod.Set(serverSpan, r.Method)
59 | ext.HTTPUrl.Set(serverSpan, r.URL.Path)
60 |
61 | // Convert the path and HTTP method into an identifier represented by a single
62 | // integer (for compression purposes)
63 | routeID, foundRoute := identifyRoute(r.URL.Path, r.Method)
64 |
65 | var path, method, routeName string
66 | if foundRoute {
67 | common.Debugf(
68 | "HTTP: Got request on route #%d (%s %s)\n",
69 | routeID, strings.ToUpper(routes[routeID].Method),
70 | routes[routeID].Path,
71 | )
72 |
73 | // Generate a compressed path using the found routeID
74 | path = genCompressedPath(r.URL, routeID)
75 | method = strings.ToUpper(routes[routeID].Method)
76 | routeName = routes[routeID].Name
77 | } else {
78 | common.Debugf(
79 | "HTTP: Got request on unknown route %s %s\n",
80 | r.Method,
81 | r.URL.Path,
82 | )
83 |
84 | // Generate a compressed path
85 | path = genCompressedPath(r.URL, -1)
86 | method = r.Method
87 | }
88 |
89 | serverSpan.SetTag("route.name", routeName)
90 | common.Debug("routeName", routeName)
91 |
92 | body, err := ioutil.ReadAll(r.Body)
93 | if err != nil {
94 | handleErr(err, serverSpan)
95 | return
96 | }
97 |
98 | // Unmarshal request body JSON
99 | var decodedBody interface{}
100 | if len(body) > 0 {
101 | contentType := r.Header.Get("content-type")
102 | common.Debugf("Got request with content type: %s", contentType)
103 |
104 | if contentType != "application/json" {
105 | common.Debug("Got non-json request, ignoring")
106 |
107 | w.WriteHeader(502)
108 | return
109 | }
110 |
111 | decodedBody = json.Decode(body)
112 | }
113 |
114 | // Add authentication header to query parameters of CoAP request
115 | var origin *string
116 | if authHeader := r.Header.Get("Authorization"); len(authHeader) > 0 {
117 | if isClientRoute(r.URL.Path) {
118 | var k, v string
119 |
120 | k = "access_token"
121 | v = strings.Replace(authHeader, "Bearer ", "", 1)
122 |
123 | var sep string
124 | if strings.Contains(path, "?") {
125 | sep = "&"
126 | } else {
127 | sep = "?"
128 | }
129 |
130 | path = path + sep + k + "=" + v
131 | } else {
132 | submatch := fedAuthRgxp.FindAllStringSubmatch(authHeader, 1)[0][1]
133 | origin = &submatch
134 | }
135 | }
136 |
137 | if len(path) == 0 {
138 | path = "/"
139 | }
140 |
141 | common.Debugf("Final path: %s", path)
142 |
143 | // Send the CoAP request to another instance of the CoAP proxy and receive a response
144 | pl, statusCode, err := sendCoAPRequest(ctx, method, r.Host, path, routeName, decodedBody, origin)
145 | if err != nil {
146 | handleErr(err, serverSpan)
147 | return
148 | }
149 |
150 | ext.HTTPStatusCode.Set(serverSpan, statusCoAPToHTTP(statusCode))
151 |
152 | w.Header().Set("Access-Control-Allow-Origin", "*")
153 | w.Header().Set("Access-Control-Allow-Headers", "content-type,authorization")
154 | w.Header().Set("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE,OPTIONS")
155 |
156 | // CoAP requests use CBOR as their encoding scheme. Decode CBOR and encode back
157 | // into JSON (if this response has a body)
158 | if len(pl) > 0 {
159 | pl = json.Encode(cbor.Decode(pl))
160 |
161 | w.Header().Set("Content-Type", "application/json")
162 | w.WriteHeader(int(statusCoAPToHTTP(statusCode)))
163 | if _, err = w.Write(pl); err != nil {
164 | log.Printf("Failed to write HTTP response: %s", err.Error())
165 | }
166 | } else {
167 | w.WriteHeader(int(statusCoAPToHTTP(statusCode)))
168 | }
169 |
170 | common.Debugf("CoAP server responded with code %s", statusCode.String())
171 | common.Debug("HTTP: Sending response")
172 | }
173 |
174 | // sendHTTPRequest is a function that sends an HTTP request to a homeserver
175 | // either from a client or another homeserver in the case of federation.
176 | func sendHTTPRequest(
177 | ctx context.Context, method string, path string, payload []byte, origin string,
178 | ) (resBody []byte, statusCode int, err error) {
179 | // OpenTracing setup
180 | span, ctx := opentracing.StartSpanFromContext(ctx, "http_request")
181 | defer span.Finish()
182 |
183 | ext.SpanKindRPCClient.Set(span)
184 |
185 | // Create the request
186 | url := fmt.Sprintf("%s%s", *httpTarget, path)
187 | hReq, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader(payload))
188 | if err != nil {
189 | ext.Error.Set(span, true)
190 | span.LogFields(olog.Error(err))
191 | return
192 | }
193 |
194 | // Set headers
195 | hReq.Header.Add("Content-Type", "application/json")
196 | if len(origin) > 0 {
197 | hReq.Header.Add("Authorization", fedAuthPrefix+origin+fedAuthSuffix)
198 | }
199 |
200 | // Record request details in OpenTracing
201 | ext.HTTPMethod.Set(span, method)
202 | ext.HTTPUrl.Set(span, path)
203 | _ = opentracing.GlobalTracer().Inject(
204 | span.Context(),
205 | opentracing.HTTPHeaders,
206 | opentracing.HTTPHeadersCarrier(hReq.Header),
207 | )
208 |
209 | // Perform the request and receive the response
210 | hRes, err := httpClient.Do(hReq)
211 | if err != nil {
212 | ext.Error.Set(span, true)
213 | span.LogFields(olog.Error(err))
214 | return
215 | }
216 |
217 | // Receive the response body
218 | resBody, err = ioutil.ReadAll(hRes.Body)
219 | if err != nil {
220 | ext.Error.Set(span, true)
221 | span.LogFields(olog.Error(err))
222 | return
223 | }
224 |
225 | if len(resBody) > 0 {
226 | contentType := hRes.Header.Get("content-type")
227 | common.Debugf("Got response with content type: %s", contentType)
228 |
229 | if contentType != "application/json" {
230 | common.Debug("Got non-json request, ignoring")
231 |
232 | statusCode = 502
233 | resBody = []byte{}
234 | return
235 | }
236 | }
237 |
238 | // Record response status code in OpenTracing
239 | ext.HTTPStatusCode.Set(span, uint16(hRes.StatusCode))
240 |
241 | statusCode = hRes.StatusCode
242 |
243 | return
244 | }
245 |
--------------------------------------------------------------------------------
/jaeger.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "log"
7 | "os"
8 |
9 | "github.com/uber/jaeger-client-go"
10 | jaegercfg "github.com/uber/jaeger-client-go/config"
11 | jaegerlog "github.com/uber/jaeger-client-go/log"
12 | "github.com/uber/jaeger-lib/metrics"
13 | )
14 |
15 | // Env flags
16 | var jaegerHost, useJaeger = os.LookupEnv("SYNAPSE_JAEGER_HOST")
17 |
18 | // setupJaegerTracing is a function that sets up OpenTracing with a
19 | // configuration specific to the CoAP proxy.
20 | func setupJaegerTracing() io.Closer {
21 | jaegerHost := jaegerHost
22 | serverName := os.Getenv("SYNAPSE_SERVER_NAME")
23 |
24 | serviceName := fmt.Sprintf("proxy-%s", serverName)
25 |
26 | var cfg jaegercfg.Configuration
27 |
28 | if useJaeger {
29 | cfg = jaegercfg.Configuration{
30 | Sampler: &jaegercfg.SamplerConfig{
31 | Type: jaeger.SamplerTypeConst,
32 | Param: 1,
33 | },
34 | Reporter: &jaegercfg.ReporterConfig{
35 | LogSpans: true,
36 | LocalAgentHostPort: fmt.Sprintf("%s:6831", jaegerHost),
37 | },
38 | ServiceName: serviceName,
39 | }
40 | } else {
41 | cfg = jaegercfg.Configuration{}
42 | }
43 |
44 | // Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
45 | // and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
46 | // frameworks.
47 | jLogger := jaegerlog.StdLogger
48 | jMetricsFactory := metrics.NullFactory
49 |
50 | // Initialize tracer with a logger and a metrics factory
51 | closer, err := cfg.InitGlobalTracer(
52 | serviceName,
53 | jaegercfg.Logger(jLogger),
54 | jaegercfg.Metrics(jMetricsFactory),
55 | )
56 | if err != nil {
57 | log.Printf("Could not initialize jaeger tracer: %s", err.Error())
58 | return nil
59 | }
60 |
61 | return closer
62 | }
63 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 New Vector Ltd
2 | //
3 | // This file is part of coap-proxy.
4 | //
5 | // coap-proxy is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // coap-proxy is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with coap-proxy. If not, see .
17 |
18 | package main
19 |
20 | import (
21 | "errors"
22 | "flag"
23 | "log"
24 | "net/http"
25 | "path/filepath"
26 | "regexp"
27 | "runtime/debug"
28 | "sync"
29 |
30 | "github.com/matrix-org/coap-proxy/common"
31 | "github.com/matrix-org/coap-proxy/types"
32 |
33 | "github.com/matrix-org/go-coap"
34 | )
35 |
36 | var (
37 | // CLI flags
38 | onlyCoAP = flag.Bool("only-coap", false, "Only proxy CoAP requests to HTTP and not the other way around")
39 | onlyHTTP = flag.Bool("only-http", false, "Only proxy HTTP requests to CoAP and not the other way around")
40 | noEncryption = flag.Bool("disable-encryption", false, "Disable noise encryption")
41 | debugLog = flag.Bool("debug-log", false, "Output debug logs")
42 | mapsDir = flag.String("maps-dir", "maps", "Directory in which the JSON maps live")
43 | coapTarget = flag.String("coap-target", "", "Force the host+port of the CoAP server to talk to")
44 | httpTarget = flag.String("http-target", "http://127.0.0.1:8008", "Force the host+port of the HTTP server to talk to")
45 | coapPort = flag.String("coap-port", "5683", "The CoAP port to listen on")
46 | coapBindHost = flag.String("coap-bind-host", "0.0.0.0", "The COAP host to listen on")
47 | httpPort = flag.String("http-port", "8888", "The HTTP port to listen on")
48 |
49 | fedAuthPrefix = "X-Matrix origin="
50 | fedAuthSuffix = ",key=\"\",sig=\"\""
51 |
52 | routePatternRgxp = regexp.MustCompile("{[^/]+}")
53 | fedAuthRgxp = regexp.MustCompile(fedAuthPrefix + "([^,]+)")
54 |
55 | // Slices to keep parsed json dictionary data in
56 | routes = make([]route, 0)
57 | eventTypes = make([]string, 0)
58 | errorCodes = make([]string, 0)
59 | queryParams = make([]string, 0)
60 |
61 | // CBOR encoder/decoder
62 | cbor = new(types.CBOR)
63 |
64 | // JSON encoder/decoder
65 | json = new(types.JSON)
66 |
67 | // Implementation of go-coap compressor struct
68 | compressor *types.Compressor
69 | )
70 |
71 | func init() {
72 | log.Printf("Starting up...")
73 |
74 | flag.Parse()
75 |
76 | if *debugLog {
77 | common.EnableDebugLogging()
78 | }
79 |
80 | if *noEncryption {
81 | log.Printf("Encryption disabled")
82 | } else {
83 | log.Printf("Encryption enabled")
84 | }
85 |
86 | conns = make(map[string]*openConn)
87 |
88 | var err error
89 |
90 | // Parse maps for later compression purposes. These allow for compression
91 | // something like a known Matrix API endpoint route down into a single integer.
92 | if err = json.ParseFile(
93 | filepath.Join(*mapsDir, "routes.json"),
94 | &routes,
95 | ); err != nil {
96 | panic(err)
97 | }
98 |
99 | if err = json.ParseFile(
100 | filepath.Join(*mapsDir, "query_params.json"),
101 | &queryParams,
102 | ); err != nil {
103 | panic(err)
104 | }
105 |
106 | if err = json.ParseFile(
107 | filepath.Join(*mapsDir, "event_types.json"),
108 | &eventTypes,
109 | ); err != nil {
110 | panic(err)
111 | }
112 |
113 | if compressor, err = types.NewCompressor(*mapsDir, []string{
114 | "event_types.json",
115 | "common_keys.json",
116 | "error_codes.json",
117 | "edu_types.json",
118 | }, cbor); err != nil {
119 | panic(err)
120 | }
121 |
122 | log.Println("Finished loading compression maps")
123 | }
124 |
125 | func main() {
126 | closer := setupJaegerTracing()
127 | if closer != nil {
128 | defer closer.Close()
129 | }
130 |
131 | // Create a wait group to keep main routine alive while HTTP and CoAP servers run in separate routines
132 | wg := sync.WaitGroup{}
133 | var h *handler
134 |
135 | // Start CoAP listener
136 | // Listens for CoAP requests and sends out HTTP
137 | if !*onlyHTTP {
138 | wg.Add(1)
139 | go func() {
140 | defer wg.Done()
141 | coapAddr := *coapBindHost + ":" + *coapPort
142 | log.Printf("Setting up CoAP to HTTP proxy on %s", coapAddr)
143 | log.Println(listenAndServe(coapAddr, "udp", coapRecoverWrap(coap.HandlerFunc(ServeCOAP)), compressor))
144 | log.Println("CoAP to HTTP proxy exited")
145 | }()
146 | }
147 |
148 | // Start HTTP listener
149 | // Listens for HTTP requests and sends out CoAP
150 | if !*onlyCoAP {
151 | wg.Add(1)
152 | go func() {
153 | defer wg.Done()
154 | httpAddr := "0.0.0.0:" + *httpPort
155 | log.Printf("Setting up HTTP to CoAP proxy on %s", httpAddr)
156 | log.Println(http.ListenAndServe(httpAddr, httpRecoverWrap(h)))
157 | log.Println("HTTP to CoAP proxy exited")
158 | }()
159 | }
160 |
161 | wg.Wait()
162 |
163 | // Close all open CoAP connections on program termination
164 | for _, c := range conns {
165 | if err := c.Close(); err != nil {
166 | panic(err)
167 | }
168 | }
169 | }
170 |
171 | func httpRecoverWrap(h http.Handler) http.Handler {
172 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
173 | var err error
174 | defer func() {
175 | r := recover()
176 | if r != nil {
177 | switch t := r.(type) {
178 | case string:
179 | err = errors.New(t)
180 | case error:
181 | err = t
182 | default:
183 | err = errors.New("Unknown error")
184 | }
185 | log.Printf("Recovered from panic: %v", err)
186 | log.Println("Stacktrace:\n" + string(debug.Stack()))
187 | http.Error(w, err.Error(), http.StatusInternalServerError)
188 | }
189 | }()
190 | h.ServeHTTP(w, r)
191 | })
192 | }
193 |
194 | func coapRecoverWrap(h coap.Handler) coap.Handler {
195 | return coap.HandlerFunc(func(w coap.ResponseWriter, r *coap.Request) {
196 | var err error
197 | defer func() {
198 | r := recover()
199 | if r != nil {
200 | switch t := r.(type) {
201 | case string:
202 | err = errors.New(t)
203 | case error:
204 | err = t
205 | default:
206 | err = errors.New("Unknown error")
207 | }
208 | log.Printf("Recovered from panic: %v", err)
209 | log.Println("Stacktrace:\n" + string(debug.Stack()))
210 | }
211 | }()
212 | h.ServeCOAP(w, r)
213 | })
214 | }
215 |
--------------------------------------------------------------------------------
/maps/common_keys.json:
--------------------------------------------------------------------------------
1 | [
2 | "errcode",
3 | "error",
4 | "msgtype",
5 | "body",
6 | "formatted_body",
7 | "format",
8 | "avatar_url",
9 | "displayname",
10 | "membership",
11 | "url",
12 | "name",
13 | "origin",
14 | "origin_server_ts",
15 | "pdus",
16 | "edus",
17 | "sender",
18 | "type",
19 | "auth_events",
20 | "unsigned",
21 | "prev_events",
22 | "depth",
23 | "redacts",
24 | "event_id",
25 | "room_id",
26 | "state_key",
27 | "content",
28 | "edu_type",
29 | "sha256",
30 | "typing",
31 | "user_id",
32 | "m.read",
33 | "data",
34 | "event_ids"
35 | ]
36 |
--------------------------------------------------------------------------------
/maps/common_values.json:
--------------------------------------------------------------------------------
1 | [
2 | "m.text",
3 | "m.emote",
4 | "m.notice"
5 | ]
6 |
--------------------------------------------------------------------------------
/maps/edu_types.json:
--------------------------------------------------------------------------------
1 | [
2 | "m.presence_invite",
3 | "m.presence_accept",
4 | "m.presence_deny",
5 | "m.presence",
6 | "m.typing",
7 | "m.receipt"
8 | ]
9 |
--------------------------------------------------------------------------------
/maps/error_codes.json:
--------------------------------------------------------------------------------
1 | [
2 | "M_BAD_JSON",
3 | "M_BAD_STATE",
4 | "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM",
5 | "M_CAPTCHA_INVALID",
6 | "M_CAPTCHA_NEEDED",
7 | "M_CONSENT_NOT_GIVEN",
8 | "M_EXAMPLE_ERROR",
9 | "M_EXCLUSIVE",
10 | "M_FORBIDDEN",
11 | "M_GUEST_ACCESS_FORBIDDEN",
12 | "M_INCOMPATIBLE_ROOM_VERSION",
13 | "M_INVALID_PARAM",
14 | "M_INVALID_ROOM_STATE",
15 | "M_INVALID_USERNAME",
16 | "M_LIMIT_EXCEEDED",
17 | "M_MISSING_PARAM",
18 | "M_MISSING_TOKEN",
19 | "M_NOT_FOUND",
20 | "M_NOT_JSON",
21 | "M_RESOURCE_LIMIT_EXCEEDED",
22 | "M_ROOM_IN_USE",
23 | "M_SERVER_NOT_TRUSTED",
24 | "M_THREEPID_AUTH_FAILED",
25 | "M_THREEPID_DENIED",
26 | "M_THREEPID_IN_USE",
27 | "M_THREEPID_NOT_FOUND",
28 | "M_TOO_LARGE",
29 | "M_UNAUTHORIZED",
30 | "M_UNKNOWN",
31 | "M_UNKNOWN_TOKEN",
32 | "M_UNRECOGNIZED",
33 | "M_UNSUPPORTED_ROOM_VERSION",
34 | "M_USER_IN_USE",
35 | "M_WEAK_PASSWORD"
36 | ]
37 |
--------------------------------------------------------------------------------
/maps/event_types.json:
--------------------------------------------------------------------------------
1 | [
2 | "m.call.answer",
3 | "m.call.candidates",
4 | "m.call.hangup",
5 | "m.call.invite",
6 | "m.direct",
7 | "m.presence",
8 | "m.receipt",
9 | "m.room.aliases",
10 | "m.room.avatar",
11 | "m.room.canonical_alias",
12 | "m.room.create",
13 | "m.room.guest_access",
14 | "m.room.history_visibility",
15 | "m.room.join_rules",
16 | "m.room.member",
17 | "m.room.message",
18 | "m.room.message.feedback",
19 | "m.room.name",
20 | "m.room.power_levels",
21 | "m.room.redaction",
22 | "m.room.third_party_invite",
23 | "m.room.topic",
24 | "m.tag",
25 | "m.typing"
26 | ]
27 |
--------------------------------------------------------------------------------
/maps/extra_flate_data:
--------------------------------------------------------------------------------
1 | kdisplayname
2 | javatar_url
3 | mdid_not_relay
4 |
5 | fage_ts
6 | dpdus
7 | gmsgtypefm.text
8 | gcontent¢dbody
9 | dbody
10 | bhunsigned
11 | ddtab
12 | kauth_events
13 | edepth
14 | foriginhsynapse
15 | porigin_server_ts
16 | fsender
17 | typenm.room.message
18 | hevent_ido$
19 | kprev_events
20 | groom_idn!
21 | cXJGX%
22 | dtypenm.room.message
23 | dtypemm.room.member
24 | events
25 | :synapse0
26 | :synapse1
27 | :synapse2
28 | :synapse3
29 | :synapse4
30 | :synapse5
31 | :synapse7
32 | :synapse8
33 | &exclude_threaded=true
34 | chunk
35 | start
36 | end
37 | thread_id
38 | =%7B%22thread_id%22%3A0%7D&2=20&dir=b&from=
39 | transaction_id
40 | m.room.room_version
41 | m.room.power_levels
42 | m.room.join_rule
43 | m.room.guest_access
44 | user_id
45 | dtypenm.room.aliases
46 | dtypevm.room.canonical_aliasfsender
47 | device_id
48 | home_server
49 | access_token
50 |
51 | "dpdus\x81\xaadtypenm.room.messageedepth"
52 | "gcontent\xa2dbody"
53 | "gmsgtypefm.textgroom_idn"
54 | "porigin_server_ts\x1b\x00\x00\x01gZ\xe3\xfd\x1c"
55 | "\x11*\xd1\x02\x06\xd1\x14"
56 | "B\x03\x00:\x8d\x87\xb10\x021Q\x00\x11* \xd1\x00\x06\xd2\x14\x01\x06\xff\xa1dpdus\x81\xaadtypenm.room.messageedepth\x18"
57 | "bE\x00\x01\x1dr\xc1*\xb1\x06Q\x07\xff\xa1dpdus\xa0"
58 | "\xa1dpdus\xa0"
59 | "\xa8erooms\xa3djoin\xa0eleave\xa0finvite\xa0fgroups\xa3djoin\xa0eleave\xa0finvite\xa0hpresence\xa1fevents\x80ito_device\xa1fevents\x80jnext_batchts58_10_0_1_1_1_1_3_1laccount_data\xa1fevents\x80ldevice_lists\xa2dleft\x80gchanged\x80x\x1adevice_one_time_keys_count\xa1qsigned_curve25519\x182"
60 | "\xa8erooms\xa3djoin\xa1n!DQQ0:synapse0\xa6estate\xa1fevents\x80gsummary\xa0htimeline\xa3fevents\x81\xa6dtypenm.room.messagefsender"
61 | "`Zy\x1eglimited\xf4jprev_batchss16_3_0_1_1_1_1_3_1iephemeral\xa1fevents\x80laccount_data\xa1fevents\x80tunread_notifications\xa0eleave"
62 | "ephemeral\xa1fevents\x80laccount_data\xa1fevents\x81\xa2dtypelm.fully_readgcontent\xa1hevent_idk"
63 | "chunk\x8a\xaacage\x0cdtypemm.room.memberfsenderqgcontent\xa3javatar_url\xf6jmembershipdjoinkdisplayname\xf6groom_id"
64 | "gcontent\xa1rhistory_visibilityfsharedgroom_id"
65 | "dtypex\x19m.room.history_visibility"
66 | "gcontent\xa1ijoin_rulefpublicgroom_idn"
67 | "dtypesm.room.power_levelsfsenderq"
68 | "gcontent\xa9cban\x182dkick\x182eusers\xa1q"
69 | "\x18dfevents\xa5km.room.name\x182mm.room.avatar\x182sm.room.power_levels\x18dvm.room.canonical_alias\x182x\x19m.room.history_visibility\x18dfinvite\x00fredact\x182mstate_default\x182musers_default\x00nevents_default\x00groom_idn"
70 | "gcontent\xa2gcreatorqlroom_versiona1groom_idn"
71 | "\xa1eflows\x81\xa1dtypepm.login.password"
72 | "\xa2eerroroNo backup foundgerrcodekM_NOT_FOUND"
73 | "xa1kdevice_keys\xa5dkeys\xa2red25519:J"
74 | "jalgorithms\x82x\x1cm.olm.v1.curve25519-aes-sha2tm.megolm.v1.aes-sha2jsignatures\xa1"
75 | "\xa2fdevice\xa0fglobal\xa5droom\x80fsender\x80gcontent\x81\xa5gactions\x83fnotify\xa2evaluegdefaultiset_tweakesound\xa1iset_tweakihighlightgdefault\xf5genabled\xf5gpatternggrule_idx\x1a.m.rule.contains_user_namehoverride\x86\xa5gactions\x81kdont_notifygdefault\xf5genabled\xf4grule_idn.m.rule.masterjconditions\x80\xa5gactions\x81kdont_notifygdefault\xf5genabled\xf5grule_idx\x18.m.rule.suppress_noticesjconditions\x81\xa3ckeyocontent.msgtypedkindkevent_matchgpatternhm.notice\xa5gactions\x83fnotify\xa2evaluegdefaultiset_tweakesound\xa2evalue\xf4iset_tweakihighlightgdefault\xf5genabled\xf5grule_idu.m.rule.invite_for_mejconditions\x83\xa3ckeydtypedkindkevent_matchgpatternmm.room.member\xa3ckeyrcontent.membershipdkindkevent_matchgpatternfinvite\xa3ckeyistate_keydkindkevent_matchgpatternq\xa5gactions\x81kdont_notifygdefault\xf5genabled\xf5grule_idt.m.rule.member_eventjconditions\x81\xa3ckeydtypedkindkevent_matchgpatternmm.room.member\xa5gactions\x83fnotify\xa2evaluegdefaultiset_tweakesound\xa1iset_tweakihighlightgdefault\xf5genabled\xf5grule_idx\x1d.m.rule.contains_display_namejconditions\x81\xa1dkinducontains_display_name\xa5gactions\x82fnotify\xa2evalue\xf5iset_tweakihighlightgdefault\xf5genabled\xf5grule_idq.m.rule.roomnotifjconditions\x82\xa3ckeylcontent.bodydkindkevent_matchgpatterne@room\xa2ckeydroomdkindx\x1esender_notification_permissioniunderride\x85\xa5gactions\x83fnotify\xa2evaluedringiset_tweakesound\xa2evalue\xf4iset_tweakihighlightgdefault\xf5genabled\xf5grule_idl.m.rule.calljconditions\x81\xa3ckeydtypedkindkevent_matchgpatternmm.call.invite\xa5gactions\x83fnotify\xa2evaluegdefaultiset_tweakesound\xa2evalue\xf4iset_tweakihighlightgdefault\xf5genabled\xf5grule_idw.m.rule.room_one_to_onejconditions\x82\xa2bisa2dkindqroom_member_count\xa3ckeydtypedkindkevent_matchgpatternnm.room.message\xa5gactions\x83fnotify\xa2evaluegdefaultiset_tweakesound\xa2evalue\xf4iset_tweakihighlightgdefault\xf5genabled\xf5grule_idx!.m.rule.encrypted_room_one_to_onejconditions\x82\xa2bisa2dkindqroom_member_count\xa3ckeydtypedkindkevent_matchgpatternpm.room.encrypted\xa5gactions\x82fnotify\xa2evalue\xf4iset_tweakihighlightgdefault\xf5genabled\xf5grule_ido.m.rule.messagejconditions\x81\xa3ckeydtypedkindkevent_matchgpatternnm.room.message\xa5gactions\x82fnotify\xa2evalue\xf4iset_tweakihighlightgdefault\xf5genabled\xf5grule_idq.m.rule.encryptedjconditions\x81\xa3ckeydtypedkindkevent_matchgpatternpm.room.encrypted"
76 | "\xa1droom\xa1htimeline\xa1elimit\x14"
77 | "\xa8erooms\xa3djoin\xa1n!eYB0:synapse0\xa6estate\xa1fevents\x80gsummary\xa0htimeline\xa3fevents\x8e\xa7dtypemm.room.createfsenderq"
78 | "\xa1mone_time_keys\xa5x\x18signed_curve25519:"
79 | "\xa1sone_time_key_counts\xa1qsigned_curve25519\x05"
80 | "\xa4jexpires_in\xfb@\xac \x00\x00\x00\x00\x00jtoken_typefBearerlaccess_tokenxrmatrix_server_namehsynapse"
81 |
--------------------------------------------------------------------------------
/maps/query_params.json:
--------------------------------------------------------------------------------
1 | [
2 | "minimum_valid_until_ts",
3 | "v",
4 | "limit",
5 | "event_id",
6 | "ver",
7 | "limit",
8 | "since",
9 | "include_all_networks",
10 | "third_party_instance_id",
11 | "room_alias",
12 | "user_id",
13 | "field",
14 | "minimum_valid_until_ts",
15 | "filter",
16 | "access_token",
17 | "timeout"
18 | ]
19 |
--------------------------------------------------------------------------------
/maps/routes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "path": "/_matrix/federation/v1/send/{txnId}",
4 | "method": "put",
5 | "name": "send_transaction"
6 | },
7 | {
8 | "path": "/_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}",
9 | "method": "put"
10 | },
11 | {
12 | "path": "/_matrix/client/r0/profile/{userId}/displayname",
13 | "method": "get"
14 | },
15 | {
16 | "path": "/_matrix/client/r0/profile/{userId}/displayname",
17 | "method": "put"
18 | },
19 | {
20 | "path": "/_matrix/client/r0/rooms/{roomId}/join",
21 | "method": "post"
22 | },
23 | {
24 | "path": "/_matrix/client/r0/rooms/{roomId}/kick",
25 | "method": "post"
26 | },
27 | {
28 | "path": "/_matrix/client/r0/admin/whois/{userId}",
29 | "method": "get"
30 | },
31 | {
32 | "path": "/_matrix/client/r0/rooms/{roomId}/receipt/{receiptType}/{eventId}",
33 | "method": "post"
34 | },
35 | {
36 | "path": "/_matrix/client/versions",
37 | "method": "get"
38 | },
39 | {
40 | "path": "/_matrix/media/r0/config",
41 | "method": "get"
42 | },
43 | {
44 | "path": "/_matrix/media/r0/download/{serverName}/{mediaId}",
45 | "method": "get"
46 | },
47 | {
48 | "path": "/_matrix/client/r0/rooms/{roomId}/invite ",
49 | "method": "post"
50 | },
51 | {
52 | "path": "/_matrix/client/r0/join/{roomIdOrAlias}",
53 | "method": "post"
54 | },
55 | {
56 | "path": "/_matrix/client/r0/presence/list/{userId}",
57 | "method": "get"
58 | },
59 | {
60 | "path": "/_matrix/client/r0/presence/list/{userId}",
61 | "method": "post"
62 | },
63 | {
64 | "path": "/_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}",
65 | "method": "delete"
66 | },
67 | {
68 | "path": "/_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}",
69 | "method": "get"
70 | },
71 | {
72 | "path": "/_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}",
73 | "method": "put"
74 | },
75 | {
76 | "path": "/_matrix/client/r0/rooms/{roomId}/state/{eventType}",
77 | "method": "get"
78 | },
79 | {
80 | "path": "/_matrix/client/r0/rooms/{roomId}/state/{eventType}",
81 | "method": "put"
82 | },
83 | {
84 | "path": "/_matrix/client/r0/account/whoami",
85 | "method": "get"
86 | },
87 | {
88 | "path": "/_matrix/client/r0/devices",
89 | "method": "get"
90 | },
91 | {
92 | "path": "/_matrix/client/r0/keys/claim",
93 | "method": "post"
94 | },
95 | {
96 | "path": "/_matrix/client/r0/login",
97 | "method": "get"
98 | },
99 | {
100 | "path": "/_matrix/client/r0/login",
101 | "method": "post"
102 | },
103 | {
104 | "path": "/_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions",
105 | "method": "put"
106 | },
107 | {
108 | "path": "/_matrix/client/r0/register/available",
109 | "method": "get"
110 | },
111 | {
112 | "path": "/_matrix/client/r0/user/{userId}/filter",
113 | "method": "post"
114 | },
115 | {
116 | "path": "/_matrix/client/r0/user_directory/search",
117 | "method": "post"
118 | },
119 | {
120 | "path": "/_matrix/client/r0/account/3pid",
121 | "method": "get"
122 | },
123 | {
124 | "path": "/_matrix/client/r0/account/3pid",
125 | "method": "post"
126 | },
127 | {
128 | "path": "/_matrix/client/r0/publicRooms",
129 | "method": "get"
130 | },
131 | {
132 | "path": "/_matrix/client/r0/publicRooms",
133 | "method": "post"
134 | },
135 | {
136 | "path": "/_matrix/client/r0/register",
137 | "method": "post"
138 | },
139 | {
140 | "path": "/_matrix/client/r0/rooms/{roomId}/ban",
141 | "method": "post"
142 | },
143 | {
144 | "path": "/_matrix/client/r0/rooms/{roomId}/typing/{userId}",
145 | "method": "put"
146 | },
147 | {
148 | "path": "/_matrix/client/r0/search",
149 | "method": "post"
150 | },
151 | {
152 | "path": "/_matrix/client/r0/account/password",
153 | "method": "post"
154 | },
155 | {
156 | "path": "/_matrix/client/r0/initialSync",
157 | "method": "get"
158 | },
159 | {
160 | "path": "/_matrix/client/r0/logout/all",
161 | "method": "post"
162 | },
163 | {
164 | "path": "/_matrix/client/r0/account/deactivate",
165 | "method": "post"
166 | },
167 | {
168 | "path": "/_matrix/client/r0/rooms/{roomId}/forget",
169 | "method": "post"
170 | },
171 | {
172 | "path": "/_matrix/client/r0/rooms/{roomId}/redact/{eventId}/{txnId}",
173 | "method": "put"
174 | },
175 | {
176 | "path": "/_matrix/client/r0/rooms/{roomId}/unban",
177 | "method": "post"
178 | },
179 | {
180 | "path": "/_matrix/client/r0/keys/query",
181 | "method": "post"
182 | },
183 | {
184 | "path": "/_matrix/client/r0/user/{userId}/account_data/{type}",
185 | "method": "put"
186 | },
187 | {
188 | "path": "/_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}",
189 | "method": "delete"
190 | },
191 | {
192 | "path": "/_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}",
193 | "method": "put"
194 | },
195 | {
196 | "path": "/_matrix/media/r0/upload",
197 | "method": "post"
198 | },
199 | {
200 | "path": "/_matrix/client/r0/events",
201 | "method": "get"
202 | },
203 | {
204 | "path": "/_matrix/client/r0/rooms/{roomId}/context/{eventId}",
205 | "method": "get"
206 | },
207 | {
208 | "path": "/_matrix/client/r0/rooms/{roomId}/invite",
209 | "method": "post"
210 | },
211 | {
212 | "path": "/_matrix/client/r0/rooms/{roomId}/messages",
213 | "method": "get"
214 | },
215 | {
216 | "path": "/_matrix/client/r0/account/3pid/delete",
217 | "method": "post"
218 | },
219 | {
220 | "path": "/_matrix/client/r0/createRoom",
221 | "method": "post"
222 | },
223 | {
224 | "path": "/_matrix/client/r0/profile/{userId}",
225 | "method": "get"
226 | },
227 | {
228 | "path": "/_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled",
229 | "method": "put"
230 | },
231 | {
232 | "path": "/_matrix/client/r0/rooms/{roomId}/leave",
233 | "method": "post"
234 | },
235 | {
236 | "path": "/_matrix/client/r0/rooms/{roomId}/members",
237 | "method": "get"
238 | },
239 | {
240 | "path": "/_matrix/client/r0/sendToDevice/{eventType}/{txnId}",
241 | "method": "put"
242 | },
243 | {
244 | "path": "/_matrix/client/r0/voip/turnServer",
245 | "method": "get"
246 | },
247 | {
248 | "path": "/.well-known/matrix/client",
249 | "method": "get"
250 | },
251 | {
252 | "path": "/_matrix/client/r0/directory/list/appservice/{networkId}/{roomId}",
253 | "method": "put"
254 | },
255 | {
256 | "path": "/_matrix/client/r0/keys/upload",
257 | "method": "post"
258 | },
259 | {
260 | "path": "/_matrix/client/r0/pushrules/",
261 | "method": "get"
262 | },
263 | {
264 | "path": "/_matrix/client/r0/rooms/{roomId}/initialSync",
265 | "method": "get"
266 | },
267 | {
268 | "path": "/_matrix/client/r0/user/{userId}/openid/request_token",
269 | "method": "post"
270 | },
271 | {
272 | "path": "/_matrix/client/r0/user/{userId}/rooms/{roomId}/tags",
273 | "method": "get"
274 | },
275 | {
276 | "path": "/_matrix/media/r0/download/{serverName}/{mediaId}/{fileName}",
277 | "method": "get"
278 | },
279 | {
280 | "path": "/_matrix/client/r0/delete_devices",
281 | "method": "post"
282 | },
283 | {
284 | "path": "/_matrix/client/r0/events/{eventId}",
285 | "method": "get"
286 | },
287 | {
288 | "path": "/_matrix/client/r0/profile/{userId}/avatar_url",
289 | "method": "get"
290 | },
291 | {
292 | "path": "/_matrix/client/r0/profile/{userId}/avatar_url",
293 | "method": "put"
294 | },
295 | {
296 | "path": "/_matrix/client/r0/pushers/set",
297 | "method": "post"
298 | },
299 | {
300 | "path": "/_matrix/client/r0/rooms/{roomId}/event/{eventId}",
301 | "method": "get"
302 | },
303 | {
304 | "path": "/_matrix/client/r0/rooms/{roomId}/report/{eventId}",
305 | "method": "post"
306 | },
307 | {
308 | "path": "/_matrix/media/r0/preview_url",
309 | "method": "get"
310 | },
311 | {
312 | "path": "/_matrix/client/r0/directory/room/{roomAlias}",
313 | "method": "delete"
314 | },
315 | {
316 | "path": "/_matrix/client/r0/directory/room/{roomAlias}",
317 | "method": "get"
318 | },
319 | {
320 | "path": "/_matrix/client/r0/directory/room/{roomAlias}",
321 | "method": "put"
322 | },
323 | {
324 | "path": "/_matrix/client/r0/sync",
325 | "method": "get"
326 | },
327 | {
328 | "path": "/_matrix/client/r0/rooms/{roomId}/read_markers",
329 | "method": "post"
330 | },
331 | {
332 | "path": "/_matrix/client/r0/logout",
333 | "method": "post"
334 | },
335 | {
336 | "path": "/_matrix/client/r0/notifications",
337 | "method": "get"
338 | },
339 | {
340 | "path": "/_matrix/client/r0/rooms/{roomId}/joined_members",
341 | "method": "get"
342 | },
343 | {
344 | "path": "/_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}",
345 | "method": "get"
346 | },
347 | {
348 | "path": "/_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}",
349 | "method": "put"
350 | },
351 | {
352 | "path": "/_matrix/client/r0/user/{userId}/filter/{filterId}",
353 | "method": "get"
354 | },
355 | {
356 | "path": "/_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}",
357 | "method": "put"
358 | },
359 | {
360 | "path": "/_matrix/client/r0/joined_rooms",
361 | "method": "get"
362 | },
363 | {
364 | "path": "/_matrix/client/r0/keys/changes",
365 | "method": "get"
366 | },
367 | {
368 | "path": "/_matrix/client/r0/presence/{userId}/status",
369 | "method": "get"
370 | },
371 | {
372 | "path": "/_matrix/client/r0/presence/{userId}/status",
373 | "method": "put"
374 | },
375 | {
376 | "path": "/_matrix/client/r0/pushers",
377 | "method": "get"
378 | },
379 | {
380 | "path": "/_matrix/client/r0/rooms/{roomId}/state",
381 | "method": "get"
382 | },
383 | {
384 | "path": "/_matrix/media/r0/thumbnail/{serverName}/{mediaId}",
385 | "method": "get"
386 | },
387 | {
388 | "path": "/_matrix/client/r0/devices/{deviceId}",
389 | "method": "delete"
390 | },
391 | {
392 | "path": "/_matrix/client/r0/devices/{deviceId}",
393 | "method": "get"
394 | },
395 | {
396 | "path": "/_matrix/client/r0/devices/{deviceId}",
397 | "method": "put"
398 | },
399 | {
400 | "path": "/_matrix/federation/v1/backfill/{roomId}",
401 | "method": "get"
402 | },
403 | {
404 | "path": "/_matrix/federation/v1/get_missing_events/{roomId}",
405 | "method": "post"
406 | },
407 | {
408 | "path": "/_matrix/federation/v1/event_auth/{roomId}/{eventId}",
409 | "method": "get"
410 | },
411 | {
412 | "path": "/_matrix/federation/v1/query_auth/{roomId}/{eventId}",
413 | "method": "post"
414 | },
415 | {
416 | "path": "/_matrix/federation/v1/state/{roomId}",
417 | "method": "get"
418 | },
419 | {
420 | "path": "/_matrix/federation/v1/state_ids/{roomId}",
421 | "method": "get"
422 | },
423 | {
424 | "path": "/_matrix/federation/v1/event/{eventId}",
425 | "method": "get"
426 | },
427 | {
428 | "path": "/_matrix/federation/v1/invite/{roomId}/{eventId}",
429 | "method": "put"
430 | },
431 | {
432 | "path": "/_matrix/federation/v1/make_join/{roomId}/{userId}",
433 | "method": "get"
434 | },
435 | {
436 | "path": "/_matrix/federation/v1/send_join/{roomId}/{eventId}",
437 | "method": "put"
438 | },
439 | {
440 | "path": "/_matrix/federation/v1/query/{serverName}/{keyId}",
441 | "method": "get"
442 | },
443 | {
444 | "path": "/_matrix/federation/v1/query",
445 | "method": "post"
446 | },
447 | {
448 | "path": "/_matrix/federation/v1/server/{keyId}",
449 | "method": "get"
450 | },
451 | {
452 | "path": "/_matrix/federation/v1/make_leave/{roomId}/{userId}",
453 | "method": "get"
454 | },
455 | {
456 | "path": "/_matrix/federation/v1/send_leave/{roomId}/{eventId}",
457 | "method": "put"
458 | },
459 | {
460 | "path": "/_matrix/federation/v1/openid/userinfo",
461 | "method": "get"
462 | },
463 | {
464 | "path": "/_matrix/federation/v1/publicRooms",
465 | "method": "get"
466 | },
467 | {
468 | "path": "/_matrix/federation/v1/query/directory",
469 | "method": "get"
470 | },
471 | {
472 | "path": "/_matrix/federation/v1/query/profile",
473 | "method": "get"
474 | },
475 | {
476 | "path": "/_matrix/federation/v1/query/{queryType}",
477 | "method": "get"
478 | },
479 | {
480 | "path": "/_matrix/federation/v1/query_auth/{roomId}/{eventId}",
481 | "method": "post"
482 | },
483 | {
484 | "path": "/_matrix/federation/v1/state/{roomId}",
485 | "method": "get"
486 | },
487 | {
488 | "path": "/_matrix/federation/v1/state_ids/{roomId}",
489 | "method": "get"
490 | },
491 | {
492 | "path": "/_matrix/federation/v1/event/{eventId}",
493 | "method": "get"
494 | },
495 | {
496 | "path": "/_matrix/federation/v1/event_auth/{roomId}/{eventId}",
497 | "method": "get"
498 | },
499 | {
500 | "path": "/_matrix/federation/v1/exchange_third_party_invite/{roomId}",
501 | "method": "put"
502 | },
503 | {
504 | "path": "/_matrix/federation/v1/3pid/onbind",
505 | "method": "put"
506 | },
507 | {
508 | "path": "/_matrix/federation/v1/user/devices/{userId}",
509 | "method": "get"
510 | },
511 | {
512 | "path": "/_matrix/federation/v1/user/keys/claim",
513 | "method": "post"
514 | },
515 | {
516 | "path": "/_matrix/federation/v1/user/keys/query",
517 | "method": "post"
518 | }
519 | ]
520 |
--------------------------------------------------------------------------------
/net.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | "github.com/matrix-org/coap-proxy/common"
8 |
9 | "github.com/matrix-org/go-coap"
10 | )
11 |
12 | // listenAndServe is a function that wraps around a CoAP server with a
13 | // specialised configuration and asks it to listen on the given address and
14 | // port.
15 | func listenAndServe(addr string, network string, handler coap.Handler, comp coap.Compressor) error {
16 | blockWiseTransfer := true
17 | blockWiseTransferSzx := coap.BlockWiseSzx1024
18 | server := &coap.Server{
19 | Addr: addr,
20 | Net: network,
21 | Handler: handler,
22 | BlockWiseTransfer: &blockWiseTransfer,
23 | BlockWiseTransferSzx: &blockWiseTransferSzx,
24 | MaxMessageSize: ^uint32(0),
25 | Encryption: !(*noEncryption),
26 | KeyStore: keyStore,
27 | Compressor: comp,
28 | RetriesQueue: retriesQueue,
29 | }
30 | return server.ListenAndServe()
31 | }
32 |
33 | // dialTimeout is a function that dials (connects to) a CoAP server as a CoAP
34 | // client and times out on a given timeout.Duration.
35 | func dialTimeout(network, address string, timeout time.Duration) (*coap.ClientConn, error) {
36 | blockWiseTransfer := true
37 | blockWiseTransferSzx := coap.BlockWiseSzx1024
38 | client := coap.Client{
39 | Net: network,
40 | DialTimeout: timeout,
41 | BlockWiseTransfer: &blockWiseTransfer,
42 | BlockWiseTransferSzx: &blockWiseTransferSzx,
43 | MaxMessageSize: ^uint32(0),
44 | Encryption: !(*noEncryption),
45 | KeyStore: keyStore,
46 | Compressor: compressor,
47 | RetriesQueue: retriesQueue,
48 | }
49 | return client.Dial(address)
50 | }
51 |
52 | // statusCoAPToHTTP is a function that converts a CoAP status code to its
53 | // equivalent HTTP status code.
54 | func statusCoAPToHTTP(coapCode coap.COAPCode) uint16 {
55 | switch coapCode {
56 | case coap.Content:
57 | return http.StatusOK
58 | case coap.Changed:
59 | return http.StatusFound
60 | case coap.BadRequest:
61 | return http.StatusBadRequest
62 | case coap.Unauthorized:
63 | return http.StatusUnauthorized
64 | case coap.BadOption:
65 | return http.StatusConflict
66 | case coap.Forbidden:
67 | return http.StatusForbidden
68 | case coap.NotFound:
69 | return http.StatusNotFound
70 | case coap.MethodNotAllowed:
71 | return http.StatusMethodNotAllowed
72 | case coap.RequestEntityTooLarge:
73 | return http.StatusTooManyRequests
74 | case coap.InternalServerError:
75 | return http.StatusInternalServerError
76 | case coap.BadGateway:
77 | return http.StatusBadGateway
78 | case coap.ServiceUnavailable:
79 | return http.StatusServiceUnavailable
80 | case coap.GatewayTimeout:
81 | return http.StatusGatewayTimeout
82 | default:
83 | common.Debugf("Unsupported CoAP code %s", coapCode.String())
84 | return http.StatusInternalServerError
85 | }
86 | }
87 |
88 | // statusHTTPToCoAP is a function that converts an HTTP status code to its
89 | // equivalent CoAP status code.
90 | func statusHTTPToCoAP(httpCode int) coap.COAPCode {
91 | switch httpCode {
92 | case http.StatusOK:
93 | return coap.Content
94 | case http.StatusFound:
95 | return coap.Changed
96 | case http.StatusBadRequest:
97 | return coap.BadRequest
98 | case http.StatusUnauthorized:
99 | return coap.Unauthorized
100 | case http.StatusForbidden:
101 | return coap.Forbidden
102 | case http.StatusNotFound:
103 | return coap.NotFound
104 | case http.StatusTooManyRequests:
105 | return coap.RequestEntityTooLarge
106 | case http.StatusConflict:
107 | return coap.BadOption
108 | case http.StatusInternalServerError:
109 | return coap.InternalServerError
110 | default:
111 | common.Debugf("Unsupported HTTP code %d", httpCode)
112 | return coap.InternalServerError
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/openconn.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/matrix-org/coap-proxy/common"
7 |
8 | "github.com/matrix-org/go-coap"
9 | )
10 |
11 | // Map of open connections with the host address as the key. Allows us to keep
12 | // track of the last time a message was sent for timeout purposes.
13 | var conns map[string]*openConn
14 |
15 | // openConn is a struct that represents an open CoAP connection to another
16 | // coap-proxy instance. We keep a map of these for timeout tracking purposes.
17 | type openConn struct {
18 | *coap.ClientConn
19 | lastMsg time.Time
20 | killswitch chan bool
21 | dead bool
22 | }
23 |
24 | func newOpenConn(target string) (c *openConn, err error) {
25 | c = new(openConn)
26 | if c.ClientConn, err = dialTimeout("udp", target, 300*time.Second); err != nil {
27 | return
28 | }
29 | c.killswitch = make(chan bool)
30 |
31 | //go c.heartbeat()
32 |
33 | return
34 | }
35 |
36 | func (c *openConn) Close() error {
37 | c.killswitch <- true
38 | return c.ClientConn.Close()
39 | }
40 |
41 | func (c *openConn) heartbeat() {
42 | for {
43 | // Wait before sending the first heatbeat so that the handshake and the
44 | // first exchange can happen.
45 | select {
46 | case <-c.killswitch:
47 | common.Debugf("Got killswitch signal for connection to %s", c.ClientConn.RemoteAddr().String())
48 | return
49 | case <-time.After(30 * time.Second):
50 | common.Debugf("Sending heartbeat to %s", c.ClientConn.RemoteAddr().String())
51 | }
52 |
53 | if err := c.ClientConn.Ping(10 * time.Second); err != nil {
54 | common.Debugf("Connection to %s is dead", c.ClientConn.RemoteAddr().String())
55 | c.dead = true
56 | return
57 | }
58 |
59 | common.Debugf("Connection to %s is alive", c.ClientConn.RemoteAddr().String())
60 | }
61 | }
62 |
63 | // resetConn is a function that given a CoAP target (address and port), closes
64 | // any existing connections to it and opens a new one.
65 | func resetConn(target string) (*openConn, error) {
66 | if c, exists := conns[target]; exists {
67 | common.Debugf("Closing UDP connection to %s", target)
68 | _ = c.Close()
69 | }
70 |
71 | common.Debugf("Creating new UDP connection to %s", target)
72 |
73 | c, err := newOpenConn(target)
74 | if err != nil {
75 | return nil, err
76 | }
77 |
78 | conns[target] = c
79 | return c, nil
80 | }
81 |
--------------------------------------------------------------------------------
/routing.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/url"
7 | "regexp"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/matrix-org/coap-proxy/common"
12 | )
13 |
14 | const (
15 | matrixClientPrefix = "/_matrix/client"
16 | matrixMediaPrefix = "/_matrix/media"
17 | matrixFederationPrefix = "/_matrix/federation"
18 | )
19 |
20 | // route is a struct that represents items in the routes.json file, which maps
21 | // an endpoint to an ID for compression purposes.
22 | type route struct {
23 | Path string `json:"path"`
24 | Method string `json:"method"`
25 | Name string `json:"name,allowempty"`
26 | }
27 |
28 | // argsAndRouteFromPath is a function that returns the routeID (encoded integer
29 | // representing a matrix API endpoint) and any associated arguments from a given path.
30 | // An argument being roomId in `/_matrix/client/r0/rooms/{roomId}/state` for instance.
31 | func argsAndRouteFromPath(
32 | path string,
33 | ) (args []string, trailingSlash bool, routeID int, err error) {
34 | deconstructedPath := strings.Split(path, "/")
35 |
36 | common.Debugf("deconstructedPath %v", deconstructedPath)
37 |
38 | var r int64
39 | if len(deconstructedPath) == 0 {
40 | err = errors.New("Got empty path")
41 | return
42 | }
43 |
44 | if deconstructedPath[0] == "" {
45 | r, err = strconv.ParseInt(deconstructedPath[1], 32, 64)
46 | routeID = int(r)
47 | args = deconstructedPath[2:]
48 | } else {
49 | r, err = strconv.ParseInt(deconstructedPath[0], 32, 64)
50 | routeID = int(r)
51 | args = deconstructedPath[1:]
52 | }
53 |
54 | if deconstructedPath[len(deconstructedPath)-1] == "" {
55 | trailingSlash = true
56 | }
57 |
58 | return
59 | }
60 |
61 | // identifyRoute receives a route path and converts it to an identifier known by
62 | // both proxies connected to each homeserver.
63 | // Essentially something like /_matrix/federation/v1/send/{txnId} becomes `1`
64 | // The proxy then sends `1` over the wire to the other proxy, and as long as
65 | // they have the same mapping between paths and IDs, then the proxy on the other
66 | // end knows what the correct path is.
67 | func identifyRoute(path, method string) (routeID int, found bool) {
68 | patternMatcher := "[^/]*"
69 | for id, route := range routes {
70 | routeRgxpBase := "^" + route.Path + "$"
71 | matches := routePatternRgxp.FindAllString(routeRgxpBase, -1)
72 | for _, match := range matches {
73 | routeRgxpBase = strings.Replace(routeRgxpBase, match, patternMatcher, -1)
74 | }
75 | if regexp.MustCompile(routeRgxpBase).MatchString(path) {
76 | if strings.EqualFold(method, route.Method) {
77 | routeID = id
78 | found = true
79 | break
80 | }
81 | }
82 | }
83 |
84 | if found {
85 | common.Debugf("Identified route #%d", routeID)
86 | } else {
87 | common.Debugf("No route matching %s %s", strings.ToUpper(method), path)
88 | }
89 |
90 | return
91 | }
92 |
93 | // genExpandedPath decodes an encoded path retrieved from a CoAP request. It
94 | // does so using a map from compressed to expanded path and query parameter
95 | // values. This map must be the same and/or compatible on both proxies for this
96 | // to function.
97 | func genExpandedPath(
98 | srcPath string, args []string, query string, trailingSlash bool, routeID int,
99 | ) (path string, err error) {
100 | q, err := url.ParseQuery(query)
101 | if err != nil {
102 | return
103 | }
104 |
105 | if len(q.Encode()) > 0 {
106 | var buf url.Values
107 | buf = make(url.Values)
108 |
109 | for key, values := range q {
110 | if i, err := strconv.Atoi(key); err == nil {
111 | buf[queryParams[i]] = values
112 | } else {
113 | buf[key] = values
114 | }
115 | }
116 |
117 | q = buf
118 | }
119 |
120 | if routeID >= 0 {
121 | path = routes[routeID].Path
122 |
123 | if len(args) > 0 {
124 | matches := routePatternRgxp.FindAllString(path, -1)
125 | var arg string
126 | for i := 0; i < len(args) && i < len(matches); i++ {
127 | arg, err = getArgFromReq(matches[i], args[i])
128 | if err != nil {
129 | return
130 | }
131 |
132 | path = strings.Replace(path, matches[i], arg, -1)
133 | }
134 | }
135 |
136 | if trailingSlash && path[len(path)-1] != '/' {
137 | path = path + "/"
138 | }
139 | } else {
140 | path = srcPath
141 | }
142 |
143 | if len(q.Encode()) > 0 {
144 | path = path + "?" + q.Encode()
145 | }
146 |
147 | return
148 | }
149 |
150 | // genCompressedPath gets given a request path, attempts to compress the query
151 | // parameters using a map, and afterwards stitches together the potentially
152 | // compressed path and query parameters into one, which it then returns.
153 | func genCompressedPath(uri *url.URL, routeID int) string {
154 | common.Debugf("Compressing %s", uri.String())
155 |
156 | if len(uri.RawQuery) > 1 {
157 | var buf url.Values
158 | buf = make(url.Values)
159 |
160 | for key, values := range uri.Query() {
161 | common.Debugf("Compression: Processing query param %s", key)
162 |
163 | index, found := queryParamsIndex(key)
164 | if found {
165 | buf[strconv.Itoa(index)] = values
166 | } else {
167 | buf[key] = values
168 | }
169 | }
170 |
171 | common.Debugf("Ended up with query %s", buf.Encode())
172 |
173 | uri.RawQuery = buf.Encode()
174 | }
175 |
176 | var path string
177 |
178 | if routeID >= 0 {
179 | deconstructedPath := strings.Split(uri.Path, "/")
180 | deconstructedRoute := strings.Split(routes[routeID].Path, "/")
181 |
182 | args := make([]string, 0)
183 | for i := 0; i < len(deconstructedRoute) && i < len(deconstructedPath); i++ {
184 | if routePatternRgxp.MatchString(deconstructedRoute[i]) {
185 | arg := compressReqArg(deconstructedRoute[i], deconstructedPath[i])
186 | args = append(args, arg)
187 | }
188 | }
189 |
190 | if len(args) > 0 {
191 | path = fmt.Sprintf("/%s/%s", strconv.FormatInt(int64(routeID), 32), strings.Join(args, "/"))
192 | } else {
193 | path = fmt.Sprintf("/%s", strconv.FormatInt(int64(routeID), 32))
194 | }
195 |
196 | splitURI := strings.Split(uri.String(), "?")
197 | if splitURI[0][len(splitURI[0])-1:] == "/" {
198 | path = path + "/"
199 | }
200 | } else {
201 | path = uri.Path
202 | }
203 |
204 | if len(uri.RawQuery) > 0 {
205 | path = path + "?" + uri.Query().Encode()
206 | }
207 |
208 | common.Debugf("Ended up with compressed path %s", path)
209 |
210 | return path
211 | }
212 |
213 | // getArgFromReq is a function that retreives an argument from a request given a
214 | // pattern type.
215 | func getArgFromReq(match, arg string) (string, error) {
216 | switch match {
217 | case patternEventType:
218 | typeID, err := strconv.Atoi(arg)
219 | if err == nil {
220 | arg = eventTypes[typeID]
221 | }
222 | case patternRoomID, patternEventID, patternRoomAlias, patternUserID, patternRoomIDOrAlias:
223 | arg = getSigil(match) + arg
224 | default:
225 | }
226 |
227 | return url.PathEscape(arg), nil
228 | }
229 |
230 | // compressReqArg is a function that compresses a request argument using its
231 | // corresponding pattern type
232 | func compressReqArg(pattern, arg string) string {
233 | oldVal := arg
234 |
235 | switch pattern {
236 | case patternEventType:
237 | index, found := eventTypeIndex(arg)
238 | if found {
239 | arg = strconv.Itoa(index)
240 | }
241 | case patternRoomID, patternEventID, patternRoomAlias, patternUserID, patternRoomIDOrAlias:
242 | arg = removeSigil(pattern, arg)
243 | default:
244 | }
245 |
246 | common.Debugf("Compressing special arg %s (value: %s) into %s", pattern, oldVal, arg)
247 |
248 | return arg
249 | }
250 |
251 | func isClientRoute(path string) bool {
252 | return (strings.HasPrefix(path, matrixClientPrefix) || strings.HasPrefix(path, matrixMediaPrefix))
253 | }
254 |
--------------------------------------------------------------------------------
/substitution.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "strings"
4 |
5 | // Patterns for identifying arguments in Matrix API endpoint query paths
6 | const (
7 | patternRoomID = "{roomId}"
8 | patternRoomAlias = "{roomAlias}"
9 | patternRoomIDOrAlias = "{roomIdOrAlias}"
10 | patternEventID = "{eventId}"
11 | patternUserID = "{userId}"
12 | patternEventType = "{eventType}"
13 | )
14 |
15 | // getSigil is a function that returns a sigil for a given pattern type
16 | func getSigil(pattern string) string {
17 | switch pattern {
18 | case patternRoomID:
19 | return "!"
20 | case patternRoomAlias:
21 | return "#"
22 | case patternEventID:
23 | return "$"
24 | case patternUserID:
25 | return "@"
26 | default:
27 | return ""
28 | }
29 | }
30 |
31 | // removeSigil is a function that removes a sigil from a string given its pattern
32 | func removeSigil(pattern, arg string) string {
33 | if pattern == patternEventID || pattern == patternRoomID || pattern == patternUserID || pattern == patternRoomAlias {
34 | if arg[0] == '%' {
35 | return arg[3:]
36 | }
37 |
38 | return arg[1:]
39 | }
40 |
41 | return arg
42 | }
43 |
44 | // eventTypeIndex is a function that encodes an event type to an integer
45 | // integer using the eventTypes map.
46 | // Found is false if encoding was not possible, otherwise true.
47 | func eventTypeIndex(t string) (index int, found bool) {
48 | for i, eventType := range eventTypes {
49 | if strings.EqualFold(t, eventType) {
50 | index = i
51 | found = true
52 | break
53 | }
54 | }
55 |
56 | return
57 | }
58 |
59 | // matrixErrorIndex is a function that encodes a known matrix error (e.g.
60 | // M_UNKNOWN) as an integer using the errorCodes map.
61 | // Found is false if encoding was not possible, otherwise true.
62 | func matrixErrorIndex(errCode string) (index int, found bool) {
63 | for i, code := range errorCodes {
64 | if strings.EqualFold(errCode, code) {
65 | index = i
66 | found = true
67 | break
68 | }
69 | }
70 |
71 | return
72 | }
73 |
74 | // queryParamsIndex is a function that encodes a query parameter key as an
75 | // integer using the queryParams map.
76 | // Found is false if encoding was not possible, otherwise true.
77 | func queryParamsIndex(key string) (index int, found bool) {
78 | for i, qp := range queryParams {
79 | if key == qp {
80 | index = i
81 | found = true
82 | break
83 | }
84 | }
85 |
86 | return
87 | }
88 |
--------------------------------------------------------------------------------
/types/cbor.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 New Vector Ltd
2 | //
3 | // This file is part of coap-proxy.
4 | //
5 | // coap-proxy is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // coap-proxy is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with coap-proxy. If not, see .
17 |
18 | package types
19 |
20 | import (
21 | "bytes"
22 |
23 | "github.com/matrix-org/coap-proxy/common"
24 |
25 | "github.com/ugorji/go/codec"
26 | )
27 |
28 | // CBOR is a type that allows for encoding and decoding of arbitrary types to
29 | // and from CBOR
30 | type CBOR struct{}
31 |
32 | // Encode encodes any golang struct into CBOR
33 | func (c *CBOR) Encode(val interface{}) []byte {
34 | common.DumpPayload("Encoding CBOR", val)
35 |
36 | var b bytes.Buffer
37 |
38 | var cborH codec.CborHandle
39 | cborH.Canonical = true
40 | enc := codec.NewEncoder(&b, &cborH)
41 | err := enc.Encode(val)
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | return b.Bytes()
47 |
48 | // TODO: Same as above. For some reason it also blows up in some cases on
49 | // the JSON->CBOR way if the allocated buffer is smaller than the payload.
50 | // cbr = cbr.Reset(make([]byte, 0, len(pl)*2))
51 | // return jsn.Reset(pl).Tocbor(cbr)
52 | }
53 |
54 | // Decode decodes a CBOR byte array to a golang struct
55 | func (c *CBOR) Decode(pl []byte) interface{} {
56 | common.DumpPayload("Decoding CBOR", pl)
57 |
58 | var val interface{}
59 |
60 | var cborH codec.Handle = new(codec.CborHandle)
61 | dec := codec.NewDecoderBytes(pl, cborH)
62 | err := dec.Decode(&val)
63 | if err != nil {
64 | panic(err)
65 | }
66 |
67 | return val
68 | }
69 |
--------------------------------------------------------------------------------
/types/compressor.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 New Vector Ltd
2 | //
3 | // This file is part of coap-proxy.
4 | //
5 | // coap-proxy is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // coap-proxy is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with coap-proxy. If not, see .
17 |
18 | package types
19 |
20 | import (
21 | "bytes"
22 | "compress/flate"
23 | "fmt"
24 | "io"
25 | "io/ioutil"
26 | "path/filepath"
27 | "strconv"
28 | "strings"
29 |
30 | "github.com/matrix-org/coap-proxy/common"
31 |
32 | "github.com/emef/bitfield"
33 | "github.com/ugorji/go/codec"
34 | )
35 |
36 | // destTable is a struct that contains a list of servers and a distance cost. It
37 | // is used in fanout routing for determining which servers to relay federation
38 | // traffic through.
39 | type destTable struct {
40 | cost uint
41 | servers []uint
42 | }
43 |
44 | // Compressor implements go-coap.Compressor
45 | type Compressor struct {
46 | dict []byte // dict is a dictionary of common string values to flate data
47 | cbor *CBOR // cbor is an instance of a cbor struct for de/encoding CBOR data
48 | }
49 |
50 | // NewCompressor returns a new instance of the Compressor struct with its
51 | // dictionary initialised from the given files.
52 | // Returns an error if the files couldn't be parsed or read.
53 | func NewCompressor(mapsDir string, mapFiles []string, cborStruct *CBOR) (*Compressor, error) {
54 | c := new(Compressor)
55 |
56 | var d = ""
57 |
58 | var buf []string
59 | j := new(JSON)
60 | for _, f := range mapFiles {
61 | buf = make([]string, 0)
62 |
63 | if err := j.ParseFile(
64 | filepath.Join(mapsDir, f),
65 | &buf,
66 | ); err != nil {
67 | return nil, err
68 | }
69 |
70 | for _, el := range buf {
71 | d += el
72 | }
73 | }
74 |
75 | b, err := ioutil.ReadFile(filepath.Join(mapsDir, "extra_flate_data"))
76 | if err != nil {
77 | return nil, err
78 | }
79 |
80 | var parsedBytes []byte
81 | for _, line := range strings.Split(string(b), "\n") {
82 | line = strings.TrimSpace(line)
83 | val, err := strconv.Unquote(line)
84 | if err == nil {
85 | line = val
86 | }
87 | parsedBytes = append(parsedBytes, []byte(line)...)
88 | }
89 |
90 | c.dict = append([]byte(d), parsedBytes...)
91 | c.cbor = cborStruct
92 |
93 | return c, nil
94 | }
95 |
96 | // CompressPayload compresses a given byte array
97 | func (c *Compressor) CompressPayload(j []byte) ([]byte, error) {
98 | var b bytes.Buffer
99 |
100 | // Compress the data using the specially crafted dictionary.
101 | zw, err := flate.NewWriterDict(&b, flate.BestCompression, c.dict)
102 | if err != nil {
103 | return nil, err
104 | }
105 |
106 | if _, err := io.Copy(zw, bytes.NewReader(j)); err != nil {
107 | return nil, err
108 | }
109 |
110 | if err := zw.Close(); err != nil {
111 | return nil, err
112 | }
113 |
114 | return b.Bytes(), nil
115 | }
116 |
117 | // DecompressPayload decompresses a given byte array
118 | func (c *Compressor) DecompressPayload(j []byte) ([]byte, error) {
119 | var b bytes.Buffer
120 |
121 | zr := flate.NewReaderDict(bytes.NewReader(j), c.dict)
122 |
123 | if _, err := io.Copy(&b, zr); err != nil {
124 | return nil, err
125 | }
126 |
127 | if err := zr.Close(); err != nil {
128 | return nil, err
129 | }
130 |
131 | return b.Bytes(), nil
132 | }
133 |
134 | // CompressTransaction is a function that compresses PDU bodies and destination
135 | // tables held inside a federation transaction.
136 | func (c *Compressor) CompressTransaction(val interface{}) interface{} {
137 | bodyMap, ok := val.(map[interface{}]interface{})
138 | if !ok {
139 | return val
140 | }
141 |
142 | pduSlice, ok := bodyMap["pdus"].([]interface{})
143 | if !ok {
144 | return val
145 | }
146 |
147 | for i := range pduSlice {
148 | pdu, ok := pduSlice[i].(map[interface{}]interface{})
149 | if !ok {
150 | continue
151 | }
152 |
153 | pduUnsigned, ok := pdu["unsigned"].(map[interface{}]interface{})
154 | if !ok {
155 | continue
156 | }
157 |
158 | pduUnsigned["dtab"] = c.compressDestTable(pduUnsigned["dtab"])
159 | }
160 |
161 | return val
162 | }
163 |
164 | // DecompressTransaction is a function that compresses a transaction for
165 | // federation traffic.
166 | func (c *Compressor) DecompressTransaction(val interface{}) interface{} {
167 | bodyMap, ok := val.(map[interface{}]interface{})
168 | if !ok {
169 | return val
170 | }
171 |
172 | pduSlice, ok := bodyMap["pdus"].([]interface{})
173 | if !ok {
174 | return val
175 | }
176 |
177 | for i := range pduSlice {
178 | pdu, ok := pduSlice[i].(map[interface{}]interface{})
179 | if !ok {
180 | continue
181 | }
182 |
183 | pduUnsigned, ok := pdu["unsigned"].(map[interface{}]interface{})
184 | if !ok {
185 | continue
186 | }
187 |
188 | pduUnsigned["dtab"] = c.decompressDestTable(pduUnsigned["dtab"])
189 | }
190 |
191 | return val
192 | }
193 |
194 | // compressDestTable is a function that encodes the destinations in a
195 | // destTable into an integer.
196 | func (c *Compressor) compressDestTable(val interface{}) interface{} {
197 | destTableSlice, ok := val.([]interface{})
198 | if !ok {
199 | return val
200 | }
201 |
202 | var entries []destTable
203 | for _, entry := range destTableSlice {
204 | entrySlice, ok := entry.([]interface{})
205 | if !ok {
206 | return val
207 | }
208 |
209 | if len(entrySlice) != 2 {
210 | return val
211 | }
212 |
213 | cost, ok := entrySlice[0].(uint64)
214 | if !ok {
215 | return val
216 | }
217 |
218 | var servers []uint
219 | if slice, ok := entrySlice[1].([]interface{}); ok {
220 | for _, s := range slice {
221 | if k, ok := s.(string); ok {
222 | // TODO: fs-specific stuff
223 | k = strings.Replace(k, "synapse", "", 1)
224 | u, err := strconv.ParseUint(k, 10, 64)
225 | if err == nil {
226 | servers = append(servers, uint(u))
227 | }
228 | }
229 | }
230 | }
231 |
232 | if len(servers) == 0 {
233 | continue
234 | }
235 |
236 | entries = append(entries, destTable{
237 | cost: uint(cost),
238 | servers: servers,
239 | })
240 | }
241 |
242 | finalMap := make(map[uint]bitfield.BitField)
243 |
244 | if len(entries) == 0 {
245 | return finalMap
246 | }
247 |
248 | for _, entry := range entries {
249 | var maxServer uint
250 | for _, s := range entry.servers {
251 | if maxServer < s {
252 | maxServer = s
253 | }
254 | }
255 |
256 | common.Debug("Max server is:", maxServer)
257 |
258 | field := bitfield.New(int(maxServer) + 1)
259 |
260 | for _, s := range entry.servers {
261 | field.Set(uint32(s))
262 | }
263 |
264 | finalMap[entry.cost] = field
265 | }
266 |
267 | return finalMap
268 | }
269 |
270 | // decompressDestTable is a function that decodes the destinations in a
271 | // destTable from an integer back into their original addresses.
272 | func (c *Compressor) decompressDestTable(val interface{}) interface{} {
273 | // Cheekily just serialize/deserialize to save having to manually parse stuff
274 | bytes := c.cbor.Encode(val)
275 |
276 | var destTableMap map[uint][]byte
277 |
278 | var cborH codec.Handle = new(codec.CborHandle)
279 | dec := codec.NewDecoderBytes(bytes, cborH)
280 | err := dec.Decode(&destTableMap)
281 | if err != nil {
282 | return val
283 | }
284 |
285 | var finalSlice []interface{}
286 | for cost, bits := range destTableMap {
287 | var entry []interface{}
288 | entry = append(entry, cost)
289 |
290 | parsedBitfield := bitfield.BitField(bits)
291 |
292 | var servers []string
293 | for idx := 0; idx < len(bits)*8; idx++ {
294 | if parsedBitfield.Test(uint32(idx)) {
295 | servers = append(servers, fmt.Sprintf("synapse%d", idx))
296 | }
297 | }
298 |
299 | entry = append(entry, servers)
300 | finalSlice = append(finalSlice, entry)
301 | }
302 |
303 | return finalSlice
304 | }
305 |
--------------------------------------------------------------------------------
/types/json.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 New Vector Ltd
2 | //
3 | // This file is part of coap-proxy.
4 | //
5 | // coap-proxy is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // coap-proxy is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with coap-proxy. If not, see .
17 |
18 | package types
19 |
20 | import (
21 | "bytes"
22 | "encoding/json"
23 | "io/ioutil"
24 |
25 | "github.com/matrix-org/coap-proxy/common"
26 |
27 | "github.com/ugorji/go/codec"
28 | )
29 |
30 | // JSON is a struct that contains JSON encoding and decoding methods
31 | type JSON struct{}
32 |
33 | // Encode takes an arbitrary golang object and encodes it to JSON
34 | func (j *JSON) Encode(val interface{}) []byte {
35 | var b bytes.Buffer
36 |
37 | var jsonH codec.Handle = new(codec.JsonHandle)
38 | enc := codec.NewEncoder(&b, jsonH)
39 | err := enc.Encode(val)
40 | if err != nil {
41 | panic(err)
42 | }
43 |
44 | common.DumpPayload("Encoding JSON", b.String())
45 |
46 | return b.Bytes()
47 |
48 | // TODO: We allocate a buffer with a len(pl)*2 capacity to ensure our buffer
49 | // can contain all of the JSON data (as len(jsonData) > len(cborData)). This
50 | // is far from being the most optimised way to do it, and a more efficient
51 | // way of computing the maximum size of the buffer to should be
52 | // investigated.
53 | // jsn = jsn.Reset(make([]byte, 0, len(pl)*2))
54 | // return cbr.Reset(pl).Tojson(jsn).Bytes()
55 | }
56 |
57 | // Decode takes a JSON byte array and produces a golang object
58 | func (j *JSON) Decode(pl []byte) interface{} {
59 | var val interface{}
60 |
61 | common.DumpPayload("Decoding JSON", pl)
62 |
63 | var jsonH codec.Handle = new(codec.JsonHandle)
64 | dec := codec.NewDecoderBytes(pl, jsonH)
65 | err := dec.Decode(&val)
66 | if err != nil {
67 | panic(err)
68 | }
69 |
70 | return val
71 |
72 | // TODO: Same as above. For some reason it also blows up in some cases on
73 | // the JSON->CBOR way if the allocated buffer is smaller than the payload.
74 | // cbr = cbr.Reset(make([]byte, 0, len(pl)*2))
75 | // return jsn.Reset(pl).Tocbor(cbr)
76 | }
77 |
78 | // ParseFile takes in a filepath and a target struct and fills it with the
79 | // contents of the JSON file
80 | func (j *JSON) ParseFile(filepath string, target interface{}) error {
81 | common.Debugf("Parsing file %s", filepath)
82 |
83 | b, err := ioutil.ReadFile(filepath)
84 | if err != nil {
85 | return err
86 | }
87 |
88 | if err = json.Unmarshal(b, target); err != nil {
89 | return err
90 | }
91 |
92 | return nil
93 | }
94 |
--------------------------------------------------------------------------------
/types/keystore.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 New Vector Ltd
2 | //
3 | // This file is part of coap-proxy.
4 | //
5 | // coap-proxy is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // coap-proxy is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with coap-proxy. If not, see .
17 |
18 | package types
19 |
20 | import (
21 | "net"
22 |
23 | "github.com/flynn/noise"
24 | )
25 |
26 | // InMemoryKeyStore is a struct containing remote and local Diffie Hellman keys
27 | // implemented by the noise protocol library.
28 | type InMemoryKeyStore struct {
29 | remoteKeys map[net.Addr][]byte
30 | localStaticKey noise.DHKey
31 | }
32 |
33 | // NewKeyStore is a function that creates a new InMemoryKeyStore instance
34 | func NewKeyStore() *InMemoryKeyStore {
35 | keyStore := &InMemoryKeyStore{}
36 | keyStore.remoteKeys = make(map[net.Addr][]byte)
37 | return keyStore
38 | }
39 |
40 | // GetLocalKey is a function that returns a static local key from the InMemoryKeyStore
41 | func (ks *InMemoryKeyStore) GetLocalKey() (noise.DHKey, error) {
42 | return ks.localStaticKey, nil
43 | }
44 |
45 | // SetLocalKey is a function that takes in a DHKey and inserts it into the InMemoryKeyStore
46 | func (ks *InMemoryKeyStore) SetLocalKey(key noise.DHKey) error {
47 | ks.localStaticKey = key
48 | return nil
49 | }
50 |
51 | // GetRemoteKey is a function that returns a remote key from the InMemoryKeyStore
52 | func (ks *InMemoryKeyStore) GetRemoteKey(addr net.Addr) ([]byte, error) {
53 | return ks.remoteKeys[addr], nil
54 | }
55 |
56 | // SetRemoteKey is a function that takes in a remote key and the address it is
57 | // associated with and inserts/updates it in the InMemoryKeyStore
58 | func (ks *InMemoryKeyStore) SetRemoteKey(addr net.Addr, key []byte) error {
59 | ks.remoteKeys[addr] = key
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "math/rand"
6 | "time"
7 |
8 | "github.com/opentracing/opentracing-go"
9 | "github.com/opentracing/opentracing-go/ext"
10 | olog "github.com/opentracing/opentracing-go/log"
11 | )
12 |
13 | var (
14 | s1 = rand.NewSource(time.Now().UnixNano())
15 | r1 = rand.New(s1)
16 | )
17 |
18 | func randSlice(n int) []byte {
19 | token := make([]byte, n)
20 | r1.Read(token)
21 | return token
22 | }
23 |
24 | // handleErr is a function that takes an error and an opentracing span and
25 | // performs the necessary error handling functions such and printing relevant
26 | // information and adding the error to the span.
27 | func handleErr(err error, serverSpan opentracing.Span) {
28 | ext.Error.Set(serverSpan, true)
29 | serverSpan.LogFields(olog.Error(err))
30 | log.Println("ERROR:", err)
31 | }
32 |
--------------------------------------------------------------------------------