├── .gitignore
├── 81b9768f.0
├── DigiCertHighAssuranceEVRootCA.crt
├── Dockerfile
├── LICENSE
├── README-OpenWRT.md
├── README-ZeroShell.md
├── README.md
├── ifFilter.json
├── multicast-relay.py
├── openwrt-python-encodings
├── __init__.py
├── aliases.py
└── ascii.py
├── ssdpDiscover.py
└── test_PacketRelay.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .coverage
3 | __pycache__
4 | *.pyc
5 |
--------------------------------------------------------------------------------
/81b9768f.0:
--------------------------------------------------------------------------------
1 | DigiCertHighAssuranceEVRootCA.crt
--------------------------------------------------------------------------------
/DigiCertHighAssuranceEVRootCA.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
5 | ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
6 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
7 | LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
8 | RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
9 | +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
10 | PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
11 | xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
12 | Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
13 | hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
14 | EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
15 | MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
16 | FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
17 | nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
18 | eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
19 | hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
20 | Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
21 | vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
22 | +OkuE6N36B9K
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3-alpine AS build
2 | RUN apk add --no-cache gcc linux-headers musl-dev
3 | RUN pip wheel netifaces
4 |
5 | FROM python:3-alpine
6 |
7 | COPY --from=build /netifaces*.whl /tmp
8 | RUN pip install /tmp/netifaces*.whl
9 | COPY multicast-relay.py /
10 |
11 | ENTRYPOINT [ "python", "multicast-relay.py", "--foreground" ]
12 |
--------------------------------------------------------------------------------
/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-OpenWRT.md:
--------------------------------------------------------------------------------
1 | OpenWRT installation notes
2 | --------------------------
3 |
4 | Required OpenWRT packages are 'python3-light' and 'python3-netifaces'.
5 |
6 | Note that only interfaces that have IPv4 addresses configured may be used
7 | as parameters to --interfaces. If you have bridges configured then you will
8 | probably need to specify the bridge names and not the underlying interface
9 | names.
10 |
11 |
--------------------------------------------------------------------------------
/README-ZeroShell.md:
--------------------------------------------------------------------------------
1 | Installation on ZeroShell
2 | -------------------------
3 |
4 | Required ZeroShell packages are the C/C++ development environment
5 | as well as the python interpreter.
6 |
7 | Once these are installed, you can then install the netifaces python
8 | package (at the time of writing, it is not installed by default).
9 |
10 | From a ZS shell, first enter the development shell:
11 |
12 | % develsh
13 |
14 | Before you can fetch and install netifaces, you will need to make
15 | the CA certificate for https://pypi.python.org available. Within
16 | this distribution there is a file called 81b9768f.0 - drop that into
17 | /etc/ssl/certs and keep the filename identical.
18 |
19 | Now you can fetch and install netifaces:
20 |
21 | develsh% easy_install-3.5 netifaces
22 |
23 | And thereafter, multicast-relay.py will be able to run as follows:
24 |
25 | % python ./multicast-relay.py
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Relay broadcast and multicast packets between interfaces
2 | --------------------------------------------------------
3 |
4 | Useful, for example, if you have Sonos speakers on one interface, or VLAN,
5 | and you want to be able to control them from devices on a different
6 | interface/VLAN. Similar for Chromecast devices.
7 |
8 | By default, SSDP multicast packets received on 239.255.255.250:1900 are
9 | relayed to the other interfaces listed, as well as multicast DNS packets
10 | received on 224.0.0.251:5353.
11 |
12 | Broadcast UDP packets received on port 6969 are also relayed by default:
13 | this is used by Sonos during the initial device-discovery phase, initiated
14 | by pressing either the infinity button or the play+volume up buttons,
15 | depending on your Sonos speaker.
16 |
17 | Please note that even when your devices have discovered one another, at
18 | least in the Sonos case, a unicast connection will be established from
19 | the speakers back to the controlling-telephone. You will need to make sure
20 | that IP forwarding is enabled (`echo 1 > /proc/sys/net/ipv4/ip_forward`) and
21 | that no firewalling is in place that would prevent connections being
22 | established.
23 |
24 | `usage: multicast-relay.py [-h] --interfaces INTERFACE INTERFACE [INTERFACE ...] [--noTransmitInterfaces INTERFACE ...] [-ifFilter IFFILTER] [--relay BROADCAST_OR_MULTICAST:PORT [BROADCAST_OR_MULTICAST:PORT ...]] [--noMDNS] [--noSSDP] [--noSonosDiscovery] [--oneInterface] [--homebrewNetifaces] [--wait] [--listen REMOTE_ADDRESS [REMOTE_ADDRESS ...]] [--remote REMOTE_ADDRESS] [--remotePort PORT] [--remoteRetry SECS] [--foreground] [--logfile FILE] [--verbose]`
25 |
26 | `--interfaces` specifies the >= 2 interfaces that you desire to listen to and
27 | relay between. You can specify an interface by name, by IP address, or by
28 | network/netmask combination (e.g. 10.0.0.0/24 in the last case). With certain
29 | flags below, the minimum number of interfaces drops to >= 1.
30 |
31 | `--noTransmitInterfaces` specifies interface(s) that are listen-only.
32 |
33 | `--ifFilter` specifies a JSON file where one can state that a source address
34 | A.B.C.D/M is only to be relayed to specific interfaces. This can be useful
35 | in applications such as a hotel where relaying for one guest room may only
36 | discover device(s) that are in the same guest room. See example file
37 | `ifFilter.json`.
38 |
39 | `--relay` specifies additional broadcast or multicast addresses to relay.
40 |
41 | `--noMDNS` disables mDNS relaying.
42 |
43 | `--noSSDP` disables SSDP relaying.
44 |
45 | `--noSonosDiscovery` disables broadcast udp/6969 relaying.
46 |
47 | `--oneInterface` support for one interface connected to two networks. Use with
48 | caution - watch out for packet storms (although the IP checksum list ought
49 | to still prevent such a thing from happening).
50 |
51 | `--homebrewNetifaces` attempt to use our own netifaces implementation, probably
52 | doesn't work on any other system than Linux but maybe useful for OpenWRT where
53 | it's rather tricky to compile up netifaces.
54 |
55 | `--allowNonEther` supports non-ethernet interfaces to be relayed [experimental].
56 |
57 | `--wait` indicates that the relay should wait for an IPv4 address to be assigned
58 | to each interface rather than bailing immediately if an interface is yet to be
59 | assigned an address.
60 |
61 | `--listen` for connections from the specified remote host(s) or network(s), for example `--listen 10.0.0.1 192.168.0.0/16`.
62 |
63 | `--remote` connect to the specified remote host. If either --listen or --remote
64 | are specified, then one can also specify just one local interface with --interfaces.
65 |
66 | `--remotePort` use the specified port for remote communications (default: 1900).
67 |
68 | `--remoteRetry` if the remote connection fails, wait at least this number of seconds before retrying (default: 5).
69 |
70 | `--aes` use the specified string to encrypt/decrypt data packets.
71 |
72 | `--foreground` stops the process forking itself off into the background. This
73 | flag also encourages logging to stdout as well as to the syslog.
74 |
75 | `--logfile` saves log data to the specified file.
76 |
77 | `--verbose` steps up the logging.
78 |
79 | multicast-relay.py requires the python 'netifaces' package. Install via
80 | 'easy_install netifaces' or 'pip install netifaces'. For ZeroShell users,
81 | please review [README-ZeroShell](README-ZeroShell.md) for further instructions.
82 |
83 | Al Smith
84 |
85 |
--------------------------------------------------------------------------------
/ifFilter.json:
--------------------------------------------------------------------------------
1 | {
2 | "10.0.0.0/20": ["br0", "br3"],
3 | "192.168.0.0/24": ["br2", "br3"],
4 | "192.168.1.1/32": ["br2"],
5 | "172.20.144.0/20": ["br0"]
6 | }
7 |
--------------------------------------------------------------------------------
/multicast-relay.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import binascii
5 | import errno
6 | import json
7 | import http.server
8 | import os
9 | import re
10 | import select
11 | import socket
12 | import struct
13 | import sys
14 | import threading
15 | import time
16 |
17 | # Al Smith January 2018
18 | # https://github.com/alsmith/multicast-relay
19 |
20 | class Logger():
21 | def __init__(self, foreground, logfile, verbose):
22 | self.verbose = verbose
23 |
24 | try:
25 | import logging
26 | import logging.handlers
27 | self.loggingAvailable = True
28 |
29 | logger = logging.getLogger()
30 | syslog_handler = logging.handlers.SysLogHandler()
31 | syslog_handler.setFormatter(logging.Formatter(fmt='%(name)s[%(process)d] %(levelname)s: %(message)s'))
32 | logger.addHandler(syslog_handler)
33 |
34 | if foreground:
35 | stream_handler = logging.StreamHandler(sys.stdout)
36 | stream_handler.setFormatter(logging.Formatter(fmt='%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%b-%d %H:%M:%S'))
37 | logger.addHandler(stream_handler)
38 |
39 | if logfile:
40 | file_handler = logging.FileHandler(logfile)
41 | file_handler.setFormatter(logging.Formatter(fmt='%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%b-%d %H:%M:%S'))
42 | logger.addHandler(file_handler)
43 |
44 | if verbose:
45 | logger.setLevel(logging.INFO)
46 | else:
47 | logger.setLevel(logging.WARN)
48 |
49 | except ImportError:
50 | self.loggingAvailable = False
51 |
52 | def info(self, *args, **kwargs):
53 | if self.loggingAvailable:
54 | import logging
55 | logging.getLogger(__file__).info(*args, **kwargs)
56 | elif self.verbose:
57 | print(args, kwargs)
58 |
59 | def warning(self, *args, **kwargs):
60 | if self.loggingAvailable:
61 | import logging
62 | logging.getLogger(__file__).warning(*args, **kwargs)
63 | else:
64 | print(args, kwargs)
65 |
66 | class Netifaces():
67 | def __init__(self, homebrewNetifaces, ifNameStructLen):
68 | self.homebrewNetifaces = homebrewNetifaces
69 | self.ifNameStructLen = ifNameStructLen
70 | if self.homebrewNetifaces:
71 | Netifaces.AF_LINK = 1
72 | Netifaces.AF_INET = 2
73 | self.interfaceAttrs = {}
74 | else:
75 | import netifaces
76 | Netifaces.AF_LINK = netifaces.AF_LINK
77 | Netifaces.AF_INET = netifaces.AF_INET
78 |
79 | def interfaces(self):
80 | if self.homebrewNetifaces:
81 | import array
82 | import fcntl
83 |
84 | maxInterfaces = 128
85 | bufsiz = maxInterfaces * 40
86 | nullByte = b'\0'
87 |
88 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
89 | ifNames = array.array('B', nullByte * bufsiz)
90 | ifNameLen = struct.unpack('iL', fcntl.ioctl(
91 | s.fileno(),
92 | 0x8912, # SIOCGIFCONF
93 | struct.pack('iL', bufsiz, ifNames.buffer_info()[0])
94 | ))[0]
95 |
96 | if ifNameLen % self.ifNameStructLen != 0:
97 | print('Do you need to set --ifNameStructLen? %s/%s ought to have a remainder of zero.' % (ifNameLen, self.ifNameStructLen))
98 | sys.exit(1)
99 |
100 | ifNames = ifNames.tostring()
101 | for i in range(0, ifNameLen, self.ifNameStructLen):
102 | name = ifNames[i:i+16].split(nullByte, 1)[0].decode()
103 | if not name:
104 | print('Cannot determine interface name: do you need to set --ifNameStructLen? %s/%s ought to have a remainder of zero.' % (ifNameLen, self.ifNameStructLen))
105 | sys.exit(1)
106 | ip = socket.inet_ntoa(fcntl.ioctl(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 0x8915, struct.pack('256s', str(name)))[20:24]) # SIOCGIFADDR
107 | netmask = socket.inet_ntoa(fcntl.ioctl(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 0x891b, struct.pack('256s', str(name)))[20:24]) # SIOCGIFNETMASK
108 | broadcast = socket.inet_ntoa(fcntl.ioctl(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 0x8919, struct.pack('256s', str(name)))[20:24]) # SIOCGIFBRDADDR
109 | hwaddr = ':'.join(['%02x' % ord(char) for char in fcntl.ioctl(socket.socket(socket.AF_INET, socket.SOCK_DGRAM), 0x8927, struct.pack('256s', str(name)))[18:24]]) # SIOCGIFHWADDR
110 | self.interfaceAttrs[name] = {Netifaces.AF_LINK: [{'addr': hwaddr}], Netifaces.AF_INET: [{'addr': ip, 'netmask': netmask, 'broadcast': broadcast}]}
111 | return self.interfaceAttrs.keys()
112 | else:
113 | import netifaces
114 | return netifaces.interfaces()
115 |
116 | def ifaddresses(self, interface):
117 | if self.homebrewNetifaces:
118 | return self.interfaceAttrs[interface]
119 | else:
120 | import netifaces
121 | return netifaces.ifaddresses(interface)
122 |
123 | class Cipher():
124 | def __init__(self, key):
125 | self.key = None
126 | if not key:
127 | return
128 |
129 | import Crypto.Cipher.AES
130 | import hashlib
131 |
132 | self.blockSize = Crypto.Cipher.AES.block_size
133 | self.key = hashlib.sha256(key.encode()).digest()
134 |
135 | @staticmethod
136 | def strToInt(s):
137 | return int(binascii.hexlify(s), 16)
138 |
139 | def encrypt(self, plaintext):
140 | if not self.key:
141 | return plaintext
142 |
143 | import Crypto
144 | import Crypto.Random
145 | import Crypto.Util.Counter
146 |
147 | iv = Crypto.Random.new().read(self.blockSize)
148 | ctr = Crypto.Util.Counter.new(128, initial_value=self.strToInt(iv))
149 | aes = Crypto.Cipher.AES.new(self.key, Crypto.Cipher.AES.MODE_CTR, counter=ctr)
150 | return iv + aes.encrypt(plaintext)
151 |
152 | def decrypt(self, ciphertext):
153 | if not self.key:
154 | return ciphertext
155 |
156 | import Crypto
157 | import Crypto.Util.Counter
158 |
159 | iv = ciphertext[:self.blockSize]
160 | ctr = Crypto.Util.Counter.new(128, initial_value=self.strToInt(iv))
161 | aes = Crypto.Cipher.AES.new(self.key, Crypto.Cipher.AES.MODE_CTR, counter=ctr)
162 | return aes.decrypt(ciphertext[self.blockSize:])
163 |
164 | class PacketRelay():
165 | MULTICAST_MIN = '224.0.0.0'
166 | MULTICAST_MAX = '239.255.255.255'
167 | BROADCAST = '255.255.255.255'
168 | SSDP_MCAST_ADDR = '239.255.255.250'
169 | SSDP_MCAST_PORT = 1900
170 | SSDP_UNICAST_PORT = 1901
171 | MDNS_MCAST_ADDR = '224.0.0.251'
172 | MDNS_MCAST_PORT = 5353
173 | MAGIC = b'MRLY'
174 | IPV4LEN = len(socket.inet_aton('0.0.0.0'))
175 |
176 | def __init__(self, interfaces, noTransmitInterfaces, ifFilter, waitForIP, ttl,
177 | oneInterface, homebrewNetifaces, ifNameStructLen, allowNonEther,
178 | ssdpUnicastAddr, mdnsForceUnicast, masquerade, listen, remote,
179 | remotePort, remoteRetry, noRemoteRelay, aes, logger):
180 | self.interfaces = interfaces
181 | self.noTransmitInterfaces = noTransmitInterfaces or []
182 |
183 | if ifFilter:
184 | with open(ifFilter) as fd:
185 | self.ifFilter = json.loads(fd.read().replace('\n', ' ').strip())
186 | else:
187 | self.ifFilter = {}
188 | self.ssdpUnicastAddr = ssdpUnicastAddr
189 | self.mdnsForceUnicast = mdnsForceUnicast
190 | self.wait = waitForIP
191 | self.ttl = ttl
192 | self.oneInterface = oneInterface
193 | self.allowNonEther = allowNonEther
194 | self.masquerade = masquerade or []
195 |
196 | self.nif = Netifaces(homebrewNetifaces, ifNameStructLen)
197 | self.logger = logger
198 |
199 | self.transmitters = []
200 | self.receivers = []
201 | self.etherAddrs = {}
202 | self.etherType = struct.pack('!H', 0x0800)
203 | self.udpMaxLength = 1458
204 |
205 | self.recentChecksums = []
206 |
207 | self.bindings = set()
208 |
209 | self.listenAddr = []
210 | if listen:
211 | for addr in listen:
212 | components = addr.split('/')
213 | if len(components) == 1:
214 | components.append('32')
215 | if not components[1].isdigit():
216 | raise ValueError('--listen netmask is not an integer')
217 | if int(components[1]) not in range(0, 33):
218 | raise ValueError('--listen netmask specifies an invalid netmask')
219 | self.listenAddr.append(components)
220 |
221 | self.listenSock = None
222 | if remote:
223 | self.remoteAddrs = list(map(lambda remote: {'addr': remote, 'socket': None, 'connecting': False, 'connectFailure': None}, remote))
224 | else:
225 | self.remoteAddrs = []
226 | self.remotePort = remotePort
227 | self.remoteRetry = remoteRetry
228 | self.noRemoteRelay = noRemoteRelay
229 | self.aes = Cipher(aes)
230 |
231 | self.remoteConnections = []
232 |
233 | if self.listenAddr:
234 | self.listenSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
235 | self.listenSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
236 | self.listenSock.bind(('0.0.0.0', self.remotePort))
237 | self.listenSock.listen(0)
238 | elif self.remoteAddrs:
239 | self.connectRemotes()
240 |
241 | def connectRemotes(self):
242 | for remote in self.remoteAddrs:
243 | if remote['socket']:
244 | continue
245 |
246 | # Attempt reconnection at most once every N seconds
247 | if remote['connectFailure'] and remote['connectFailure'] > time.time()-self.remoteRetry:
248 | return
249 |
250 | remoteConnection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
251 | remoteConnection.setblocking(0)
252 | self.logger.info('REMOTE: Connecting to remote %s' % remote['addr'])
253 | remote['connecting'] = True
254 | try:
255 | remoteConnection.connect((remote['addr'], self.remotePort))
256 | except socket.error as e:
257 | if e.errno == errno.EINPROGRESS:
258 | remote['socket'] = remoteConnection
259 | else:
260 | remote['connecting'] = False
261 | remote['connectFailure'] = time.time()
262 |
263 | def removeConnection(self, s):
264 | if s in self.remoteConnections:
265 | self.remoteConnections.remove(s)
266 | return
267 |
268 | for remote in self.remoteAddrs:
269 | if remote['socket'] == s:
270 | remote['socket'] = None
271 | remote['connecting'] = False
272 | remote['connectFailure'] = time.time()
273 |
274 | def remoteSockets(self):
275 | return self.remoteConnections + list(map(lambda remote: remote['socket'], filter(lambda remote: remote['socket'], self.remoteAddrs)))
276 |
277 | def addListener(self, addr, port, service):
278 | if self.isBroadcast(addr):
279 | self.etherAddrs[addr] = self.broadcastIpToMac(addr)
280 | elif self.isMulticast(addr):
281 | self.etherAddrs[addr] = self.multicastIpToMac(addr)
282 | else:
283 | # unicast -- we don't know yet which IP we'll want to send to
284 | self.etherAddrs[addr] = None
285 |
286 | rx = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
287 | rx.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
288 | rx.bind((addr, port))
289 | self.receivers.append(rx)
290 |
291 | # Set up the receiving socket and corresponding IP and interface information.
292 | # One receiving socket is required per multicast address. But for extra
293 | # fun, one receiving socket for each network interface, if we're
294 | # intercepting broadcast packets.
295 | if self.isMulticast(addr):
296 | rx = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
297 | rx.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
298 |
299 | for interface in self.interfaces:
300 | (ifname, mac, ip, netmask, broadcast) = self.getInterface(interface)
301 |
302 | # Add this interface to the receiving socket's list.
303 | if self.isBroadcast(addr):
304 | rx = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
305 | rx.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
306 | rx.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
307 |
308 | if 'SO_BINDTODEVICE' not in dir(socket):
309 | socket.SO_BINDTODEVICE = 25
310 |
311 | rx.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, ifname.encode('utf-8'))
312 |
313 | rx.bind(('0.0.0.0', port))
314 |
315 | self.receivers.append(rx)
316 | self.bindings.add((broadcast, port))
317 | listenIP = '255.255.255.255'
318 |
319 | elif self.isMulticast(addr):
320 | packedAddress = struct.pack('4s4s', socket.inet_aton(addr), socket.inet_aton(ip))
321 | rx.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, packedAddress)
322 | listenIP = addr
323 | else:
324 | listenIP = addr
325 |
326 | # Generate a transmitter socket. Each interface
327 | # requires its own transmitting socket.
328 | if interface not in self.noTransmitInterfaces:
329 | tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
330 | tx.bind((ifname, 0))
331 |
332 | self.transmitters.append({'relay': {'addr': listenIP, 'port': port}, 'interface': ifname, 'addr': ip, 'mac': mac, 'netmask': netmask, 'broadcast': broadcast, 'socket': tx, 'service': service})
333 |
334 | if self.isMulticast(addr):
335 | rx.bind((addr, port))
336 | self.receivers.append(rx)
337 | self.bindings.add((addr, port))
338 |
339 | @staticmethod
340 | def unicastIpToMac(ip, procNetArp=None):
341 | """
342 | Return the mac address (as a string) of ip
343 | If procNetArp is not None, then it will be used instead
344 | of reading /proc/net/arp (useful for unit tests).
345 | """
346 | if procNetArp:
347 | arpTable = procNetArp
348 | else:
349 | # The arp table should be fairly small -- read it all in one go
350 | with open('/proc/net/arp', 'r') as fd:
351 | arpTable = fd.read()
352 |
353 | # Format:
354 | # IP address HW type Flags HW address Mask Device
355 | # 192.168.0.1 0x1 0x2 18:90:22:bf:3c:23 * wlp2s0
356 | matches = re.findall(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s.*\s(([a-fA-F\d]{1,2}\:){5}[a-fA-F\d]{1,2})', arpTable)
357 |
358 | # We end up with tuples of 3 groups: (ip, mac, one_of_the_mac_sub_group)
359 | # We remove the 3rd one which allows us to create a dictionary:
360 | ip2mac = dict([t[0:2] for t in matches])
361 |
362 | # Default to None if key not in dict
363 | return ip2mac.get(ip, None)
364 |
365 | @staticmethod
366 | def modifyUdpPacket(data, ipHeaderLength, srcAddr=None, srcPort=None, dstAddr=None, dstPort=None):
367 | srcAddr = srcAddr if srcAddr else socket.inet_ntoa(data[12:16])
368 | dstAddr = dstAddr if dstAddr else socket.inet_ntoa(data[16:20])
369 |
370 | srcPort = srcPort if srcPort else struct.unpack('!H', data[ipHeaderLength+0:ipHeaderLength+2])[0]
371 | dstPort = dstPort if dstPort else struct.unpack('!H', data[ipHeaderLength+2:ipHeaderLength+4])[0]
372 |
373 | # Recreate the packet
374 | ipHeader = data[:ipHeaderLength-8] + socket.inet_aton(srcAddr) + socket.inet_aton(dstAddr)
375 |
376 | udpData = data[ipHeaderLength+8:]
377 | udpLength = 8 + len(udpData)
378 | udpHeader = struct.pack('!4H', srcPort, dstPort, udpLength, 0)
379 |
380 | return ipHeader + udpHeader + udpData
381 |
382 | @staticmethod
383 | def mdnsSetUnicastBit(data, ipHeaderLength):
384 | headers = data[:ipHeaderLength+8]
385 | udpData = data[ipHeaderLength+8:]
386 |
387 | flags = struct.unpack('!H', udpData[2:4])[0]
388 | if flags & 0x8000 != 0:
389 | return data
390 |
391 | queries = struct.unpack('!H', udpData[4:6])[0]
392 |
393 | queryCount = 0
394 | ptr = 12
395 | while True:
396 | labelLength = struct.unpack('B', udpData[ptr:ptr+1])[0]
397 | if not labelLength & 0x3f:
398 | if labelLength & 0xc0:
399 | ptr += 1
400 | queryCount += 1
401 | data = struct.unpack('!H', udpData[ptr+3:ptr+5])[0]
402 | udpData = udpData[:ptr+3] + struct.pack('!H', data | 0x8000) + udpData[ptr+5:]
403 | if queryCount == queries:
404 | break
405 | ptr += 5
406 | else:
407 | ptr += labelLength+1
408 |
409 | return headers + udpData
410 |
411 | def computeIPChecksum(self, data, ipHeaderLength):
412 | # Zero out current checksum
413 | data = data[:10] + struct.pack('!H', 0) + data[12:]
414 |
415 | # Recompute the IP header checksum
416 | checksum = 0
417 | for i in range(0, ipHeaderLength, 2):
418 | checksum += struct.unpack('!H', data[i:i+2])[0]
419 |
420 | while checksum > 0xffff:
421 | checksum = (checksum & 0xffff) + ((checksum - (checksum & 0xffff)) >> 16)
422 |
423 | checksum = ~checksum & 0xffff
424 | self.recentChecksums.append(checksum)
425 | if len(self.recentChecksums) > 256:
426 | self.recentChecksums = self.recentChecksums[1:]
427 |
428 | return data[:10] + struct.pack('!H', checksum) + data[12:]
429 |
430 | @staticmethod
431 | def computeUDPChecksum(ipHeader, udpHeader, data):
432 | pseudoIPHeader = ipHeader[12:20]+struct.pack('x')+ipHeader[9:10]+udpHeader[4:6]
433 |
434 | udpPacket = pseudoIPHeader+udpHeader[:6]+struct.pack('xx')+data
435 | if len(udpPacket) % 2:
436 | udpPacket += struct.pack('x')
437 |
438 | # Recompute the UDP header checksum
439 | checksum = 0
440 | for i in range(0, len(udpPacket), 2):
441 | checksum += struct.unpack('!H', udpPacket[i:i+2])[0]
442 |
443 | while checksum > 0xffff:
444 | checksum = (checksum & 0xffff) + ((checksum - (checksum & 0xffff)) >> 16)
445 |
446 | checksum = ~checksum & 0xffff
447 | return udpHeader[:6]+struct.pack('!H', checksum)
448 |
449 | def transmitPacket(self, sock, srcMac, destMac, ipHeaderLength, ipPacket):
450 | ipHeader = ipPacket[:ipHeaderLength]
451 | udpHeader = ipPacket[ipHeaderLength:ipHeaderLength+8]
452 | data = ipPacket[ipHeaderLength+8:]
453 | dontFragment = ipPacket[6]
454 | if type(dontFragment) == str:
455 | dontFragment = ord(dontFragment)
456 | dontFragment = (dontFragment & 0x40) >> 6
457 |
458 | udpHeader = self.computeUDPChecksum(ipHeader, udpHeader, data)
459 |
460 | for boundary in range(0, len(data), self.udpMaxLength):
461 | dataFragment = data[boundary:boundary+self.udpMaxLength]
462 | totalLength = len(ipHeader) + len(udpHeader) + len(dataFragment)
463 | moreFragments = boundary+self.udpMaxLength < len(data)
464 |
465 | flagsOffset = boundary & 0x1fff
466 | if moreFragments:
467 | flagsOffset |= 0x2000
468 | elif dontFragment:
469 | flagsOffset |= 0x4000
470 |
471 | ipHeader = ipHeader[:2]+struct.pack('!H', totalLength)+ipHeader[4:6]+struct.pack('!H', flagsOffset)+ipHeader[8:]
472 | ipPacket = self.computeIPChecksum(ipHeader + udpHeader + dataFragment, ipHeaderLength)
473 |
474 | try:
475 | if srcMac != binascii.unhexlify('00:00:00:00:00:00'.replace(':', '')):
476 | etherPacket = destMac + srcMac + self.etherType + ipPacket
477 | sock.send(etherPacket)
478 | else:
479 | sock.send(ipPacket)
480 | except Exception as e:
481 | if e.errno == errno.ENXIO:
482 | raise
483 | else:
484 | self.logger.info('Error sending packet: %s' % str(e))
485 |
486 | def match(self, addr, port):
487 | return ((addr, port)) in self.bindings
488 |
489 | def loop(self):
490 | # Record where the most recent SSDP searches came from, to relay unicast answers
491 | # Note: ideally we'd be more clever and record multiple, but in practice
492 | # recording the last one seems to be enough for a 'normal' home SSDP traffic
493 | # (devices tend to retry SSDP queries multiple times anyway)
494 | recentSsdpSearchSrc = {}
495 | while True:
496 | if self.remoteAddrs:
497 | self.connectRemotes()
498 |
499 | additionalListeners = []
500 | if self.listenSock:
501 | additionalListeners.append(self.listenSock)
502 | additionalListeners.extend(self.remoteSockets())
503 |
504 | try:
505 | (inputready, _, _) = select.select(additionalListeners + self.receivers, [], [], 1)
506 | except KeyboardInterrupt:
507 | break
508 | for s in inputready:
509 | if s == self.listenSock:
510 | (remoteConnection, remoteAddr) = s.accept()
511 | if not len(list(filter(lambda addr: PacketRelay.onNetwork(remoteAddr[0], addr[0], PacketRelay.cidrToNetmask(int(addr[1]))), self.listenAddr))):
512 | self.logger.info('Refusing connection from %s - not in %s' % (remoteAddr[0], self.listenAddr))
513 | remoteConnection.close()
514 | else:
515 | self.remoteConnections.append(remoteConnection)
516 | self.logger.info('REMOTE: Accepted connection from %s' % remoteAddr[0])
517 | continue
518 | else:
519 | if s in self.remoteSockets():
520 | receivingInterface = 'remote'
521 | s.setblocking(1)
522 | try:
523 | (data, _) = s.recvfrom(2, socket.MSG_WAITALL)
524 | except socket.error as e:
525 | self.logger.info('REMOTE: Connection closed (%s)' % str(e))
526 | self.removeConnection(s)
527 | continue
528 |
529 | if not data:
530 | s.close()
531 | self.logger.info('REMOTE: Connection closed')
532 | self.removeConnection(s)
533 | continue
534 |
535 | size = struct.unpack('!H', data)[0]
536 | try:
537 | (packet, _) = s.recvfrom(size, socket.MSG_WAITALL)
538 | except socket.error as e:
539 | self.logger.info('REMOTE: Connection closed (%s)' % str(e))
540 | self.removeConnection(s)
541 | continue
542 |
543 | packet = self.aes.decrypt(packet)
544 |
545 | magic = packet[:len(self.MAGIC)]
546 | addr = socket.inet_ntoa(packet[len(self.MAGIC):len(self.MAGIC)+self.IPV4LEN])
547 | data = packet[len(self.MAGIC)+self.IPV4LEN:]
548 |
549 | if magic != self.MAGIC:
550 | self.logger.info('REMOTE: Garbage data received, closing connection.')
551 | s.close()
552 | self.remoteConnection(s)
553 | continue
554 |
555 | else:
556 | receivingInterface = 'local'
557 | (data, addr) = s.recvfrom(10240)
558 | addr = addr[0]
559 |
560 | eighthDataByte = data[8]
561 | if sys.version_info > (3, 0):
562 | eighthDataByte = bytes([data[8]])
563 | ttl = struct.unpack('B', eighthDataByte)[0]
564 |
565 | if self.ttl:
566 | data = data[:8] + struct.pack('B', self.ttl) + data[9:]
567 |
568 | # Use IP checksum information to see if we have already seen this
569 | # packet, since once we have retransmitted it on an interface
570 | # we know that we will see it once again on that interface.
571 | #
572 | # If we were retransmitting via a UDP socket then we could
573 | # just disable IP_MULTICAST_LOOP but that won't work as we are
574 | # using an RAW socket.
575 | ipChecksum = struct.unpack('!H', data[10:12])[0]
576 | if ipChecksum in self.recentChecksums:
577 | continue
578 |
579 | srcAddr = socket.inet_ntoa(data[12:16])
580 | dstAddr = socket.inet_ntoa(data[16:20])
581 |
582 | # Compute the length of the IP header so that we can then move past
583 | # it and delve into the UDP packet to find out what destination port
584 | # this packet was sent to. The length is encoded in the first least
585 | # significant nybble of the IP packet and is specified in nybbles.
586 | firstDataByte = data[0]
587 | if sys.version_info > (3, 0):
588 | firstDataByte = bytes([data[0]])
589 | ipHeaderLength = (struct.unpack('B', firstDataByte)[0] & 0x0f) * 4
590 | srcPort = struct.unpack('!H', data[ipHeaderLength+0:ipHeaderLength+2])[0]
591 | dstPort = struct.unpack('!H', data[ipHeaderLength+2:ipHeaderLength+4])[0]
592 |
593 | # raw sockets cannot be bound to a specific port, so we receive all UDP packets with matching dstAddr
594 | if receivingInterface == 'local' and not self.match(dstAddr, dstPort):
595 | continue
596 |
597 | if self.remoteSockets() and not (receivingInterface == 'remote' and self.noRemoteRelay) and srcAddr != self.ssdpUnicastAddr:
598 | packet = self.aes.encrypt(self.MAGIC + socket.inet_aton(addr) + data)
599 | for remoteConnection in self.remoteSockets():
600 | if remoteConnection == s:
601 | continue
602 | try:
603 | remoteConnection.sendall(struct.pack('!H', len(packet)) + packet)
604 |
605 | for remote in self.remoteAddrs:
606 | if remote['socket'] == remoteConnection and remote['connecting']:
607 | self.logger.info('REMOTE: Connection to %s established' % remote['addr'])
608 | remote['connecting'] = False
609 | except socket.error as e:
610 | if e.errno == errno.EAGAIN:
611 | pass
612 | else:
613 | self.logger.info('REMOTE: Failed to connect to %s: %s' % (self.remoteAddr, str(e)))
614 | self.removeConnection(remoteConnection)
615 | continue
616 |
617 | origSrcAddr = srcAddr
618 | origSrcPort = srcPort
619 | origDstAddr = dstAddr
620 | origDstPort = dstPort
621 |
622 | # Record who sent the request
623 | # FIXME: record more than one?
624 | destMac = None
625 | modifiedData = None
626 |
627 | if self.mdnsForceUnicast and dstAddr == PacketRelay.MDNS_MCAST_ADDR and dstPort == PacketRelay.MDNS_MCAST_PORT:
628 | data = PacketRelay.mdnsSetUnicastBit(data, ipHeaderLength)
629 |
630 | if self.ssdpUnicastAddr and dstAddr == PacketRelay.SSDP_MCAST_ADDR and dstPort == PacketRelay.SSDP_MCAST_PORT and (re.search(b'M-SEARCH', data) or re.search(b'NOTIFY', data)):
631 | recentSsdpSearchSrc = {'addr': srcAddr, 'port': srcPort}
632 | self.logger.info('Last SSDP search source: %s:%d' % (srcAddr, srcPort))
633 |
634 | # Modify the src IP and port to make it look like it comes from us
635 | # so as we receive the unicast answers to a well known port (1901)
636 | # and can relay them
637 | srcAddr = self.ssdpUnicastAddr
638 | srcPort = PacketRelay.SSDP_UNICAST_PORT
639 | data = PacketRelay.modifyUdpPacket(data, ipHeaderLength, srcAddr=srcAddr, srcPort=srcPort)
640 |
641 | elif self.ssdpUnicastAddr and origDstAddr == self.ssdpUnicastAddr and origDstPort == PacketRelay.SSDP_UNICAST_PORT:
642 | if not recentSsdpSearchSrc:
643 | # We haven't seen a SSDP multicast request yet
644 | continue
645 |
646 | # Relay the SSDP unicast answer back to the most recent source.
647 | # On a network that has heavy SSDP usage, this probably won't
648 | # really work as designed: if the unicast reply comes after
649 | # another SSDP multicast packet comes in from a different srcAddr
650 | # then the reply goes back to the wrong host.
651 | dstAddr = recentSsdpSearchSrc['addr']
652 | dstPort = recentSsdpSearchSrc['port']
653 | self.logger.info('Received SSDP Unicast - received from %s:%d on %s:%d, need to relay to %s:%d' % (origSrcAddr, origSrcPort, origDstAddr, origDstPort, dstAddr, dstPort))
654 | data = PacketRelay.modifyUdpPacket(data, ipHeaderLength, dstAddr=dstAddr, dstPort=dstPort)
655 |
656 | try:
657 | destMac = binascii.unhexlify(PacketRelay.unicastIpToMac(dstAddr).replace(':', ''))
658 | except Exception as e:
659 | self.logger.info('DEBUG: exception while resolving mac of IP %s: %s' % (dstAddr, str(e)))
660 | continue
661 |
662 | # It's possible (though unlikely) we can't resolve the MAC if it's unicast.
663 | # In that case, we can't relay the packet.
664 | if not destMac:
665 | self.logger.info('DEBUG: could not resolve mac for %s' % dstAddr)
666 | continue
667 |
668 | # Work out the name of the interface we received the packet on.
669 | broadcastPacket = False
670 | if receivingInterface == 'local':
671 | for tx in self.transmitters:
672 | if (origDstAddr == tx['relay']['addr'] or origDstAddr == tx.get('broadcast')) and origDstPort == tx['relay']['port'] \
673 | and self.onNetwork(addr, tx['addr'], tx['netmask']):
674 | receivingInterface = tx['interface']
675 | broadcastPacket = (origDstAddr == tx['broadcast'])
676 |
677 | for tx in self.transmitters:
678 | # Re-transmit on all other interfaces than on the interface that we received this packet from...
679 | if receivingInterface == tx['interface']:
680 | continue
681 |
682 | transmit = True
683 | for net in self.ifFilter:
684 | (network, netmask) = '/' in net and net.split('/') or (net, '32')
685 | if self.onNetwork(srcAddr, network, self.cidrToNetmask(int(netmask))) and tx['interface'] not in self.ifFilter[net]:
686 | transmit = False
687 | break
688 | if not transmit:
689 | continue
690 |
691 | if srcAddr == self.ssdpUnicastAddr and not self.onNetwork(srcAddr, tx['addr'], tx['netmask']):
692 | continue
693 |
694 | if broadcastPacket:
695 | dstAddr = tx['broadcast']
696 | destMac = self.etherAddrs[PacketRelay.BROADCAST]
697 | origDstAddr = tx['broadcast']
698 | data = data[:16] + socket.inet_aton(tx['broadcast']) + data[20:]
699 |
700 | if (origDstAddr == tx['relay']['addr'] or origDstAddr == tx.get('broadcast')) and origDstPort == tx['relay']['port'] and (self.oneInterface or not self.onNetwork(addr, tx['addr'], tx['netmask'])):
701 | destMac = destMac if destMac else self.etherAddrs[dstAddr]
702 |
703 | if tx['interface'] in self.masquerade:
704 | data = data[:12] + socket.inet_aton(tx['addr']) + data[16:]
705 | srcAddr = tx['addr']
706 | asSrc = '' if srcAddr == origSrcAddr and srcPort == origSrcPort else ' (as %s:%s)' % (srcAddr, srcPort)
707 | self.logger.info('%s%s %s byte%s from %s:%s on %s [ttl %s] to %s:%s via %s/%s%s' % (tx['service'] and '[%s] ' % tx['service'] or '',
708 | tx['interface'] in self.masquerade and 'Masqueraded' or 'Relayed',
709 | len(data),
710 | len(data) != 1 and 's' or '',
711 | origSrcAddr,
712 | origSrcPort,
713 | receivingInterface,
714 | ttl,
715 | dstAddr,
716 | dstPort,
717 | tx['interface'],
718 | tx['addr'],
719 | asSrc))
720 |
721 | try:
722 | self.transmitPacket(tx['socket'], tx['mac'], destMac, ipHeaderLength, data)
723 | except Exception as e:
724 | if e.errno == errno.ENXIO:
725 | try:
726 | (ifname, mac, ip, netmask, broadcast) = self.getInterface(tx['interface'])
727 | s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
728 | s.bind((ifname, 0))
729 | tx['mac'] = mac
730 | tx['netmask'] = netmask
731 | tx['addr'] = ip
732 | tx['socket'] = s
733 | self.transmitPacket(tx['socket'], tx['mac'], destMac, ipHeaderLength, data)
734 | except Exception as e:
735 | self.logger.info('Error sending packet: %s' % str(e))
736 |
737 | def getInterface(self, interface):
738 | ifname = None
739 |
740 | # See if we got an interface name.
741 | if interface in self.nif.interfaces():
742 | ifname = interface
743 |
744 | # Maybe we got an network/netmask combination?
745 | elif re.match('\A\d+\.\d+\.\d+\.\d+\Z', interface):
746 | for i in self.nif.interfaces():
747 | addrs = self.nif.ifaddresses(i)
748 | if self.nif.AF_INET in addrs:
749 | if self.nif.AF_INET in addrs and interface == addrs[self.nif.AF_INET][0]['addr']:
750 | ifname = i
751 | break
752 |
753 | # Or perhaps we got an IP address?
754 | elif re.match('\A\d+\.\d+\.\d+\.\d+/\d+\Z', interface):
755 | (network, netmask) = interface.split('/')
756 | netmask = '.'.join([str((0xffffffff << (32 - int(netmask)) >> i) & 0xff) for i in [24, 16, 8, 0]])
757 |
758 | for i in self.nif.interfaces():
759 | addrs = self.nif.ifaddresses(i)
760 | if self.nif.AF_INET in addrs:
761 | if self.nif.AF_INET in addrs:
762 | ip = addrs[self.nif.AF_INET][0]['addr']
763 | if self.onNetwork(ip, network, netmask):
764 | ifname = i
765 | break
766 |
767 | if not ifname:
768 | raise IOError('Interface %s does not exist.' % interface)
769 |
770 | try:
771 | # Here we want to make sure that an interface has an
772 | # IPv4 address - but if we are running at boot time
773 | # it might be that we don't yet have an address assigned.
774 | #
775 | # --wait doesn't make sense in the situation where we
776 | # look for an IP# or net/mask combination, of course.
777 | while True:
778 | addrs = self.nif.ifaddresses(ifname)
779 | if self.nif.AF_INET in addrs:
780 | break
781 | if not self.wait:
782 | print('Interface %s does not have an IPv4 address assigned.' % ifname)
783 | sys.exit(1)
784 | self.logger.info('Waiting for IPv4 address on %s' % ifname)
785 | time.sleep(1)
786 |
787 | ip = addrs[self.nif.AF_INET][0]['addr']
788 | netmask = addrs[self.nif.AF_INET][0]['netmask']
789 |
790 | ipLong = PacketRelay.ip2long(ip)
791 | netmaskLong = PacketRelay.ip2long(netmask)
792 | broadcastLong = ipLong | (~netmaskLong & 0xffffffff)
793 | broadcast = PacketRelay.long2ip(broadcastLong)
794 |
795 | # If we've been given a virtual interface like eth0:0 then
796 | # netifaces might not be able to detect its MAC address so
797 | # lets at least try the parent interface and see if we can
798 | # find a MAC address there.
799 | if self.nif.AF_LINK not in addrs and ifname.find(':') != -1:
800 | addrs = self.nif.ifaddresses(ifname[:ifname.find(':')])
801 |
802 | if self.nif.AF_LINK in addrs:
803 | mac = addrs[self.nif.AF_LINK][0]['addr']
804 | elif self.allowNonEther:
805 | mac = '00:00:00:00:00:00'
806 | else:
807 | print('Unable to detect MAC address for interface %s.' % ifname)
808 | sys.exit(1)
809 |
810 | # These functions all return a value in string format, but our
811 | # only use for a MAC address later is when we concoct a packet
812 | # to send, and at that point we need as binary data. Lets do
813 | # that conversion here.
814 | return (ifname, binascii.unhexlify(mac.replace(':', '')), ip, netmask, broadcast)
815 | except Exception as e:
816 | print('Error getting information about interface %s.' % ifname)
817 | print('Valid interfaces: %s' % ' '.join(self.nif.interfaces()))
818 | self.logger.info(str(e))
819 | sys.exit(1)
820 |
821 | @staticmethod
822 | def isMulticast(ip):
823 | """
824 | Is this IP address a multicast address?
825 | """
826 | ipLong = PacketRelay.ip2long(ip)
827 | return ipLong >= PacketRelay.ip2long(PacketRelay.MULTICAST_MIN) and ipLong <= PacketRelay.ip2long(PacketRelay.MULTICAST_MAX)
828 |
829 | @staticmethod
830 | def isBroadcast(ip):
831 | """
832 | Is this IP address a broadcast address?
833 | """
834 | return ip == PacketRelay.BROADCAST
835 |
836 | @staticmethod
837 | def ip2long(ip):
838 | """
839 | Given an IP address (or netmask) turn it into an unsigned long.
840 | """
841 | packedIP = socket.inet_aton(ip)
842 | return struct.unpack('!L', packedIP)[0]
843 |
844 | @staticmethod
845 | def long2ip(ip):
846 | """
847 | Given an unsigned long turn it into an IP address
848 | """
849 | return socket.inet_ntoa(struct.pack('!I', ip))
850 |
851 | @staticmethod
852 | def onNetwork(ip, network, netmask):
853 | """
854 | Given an IP address and a network/netmask tuple, work out
855 | if that IP address is on that network.
856 | """
857 | ipL = PacketRelay.ip2long(ip)
858 | networkL = PacketRelay.ip2long(network)
859 | netmaskL = PacketRelay.ip2long(netmask)
860 | return (ipL & netmaskL) == (networkL & netmaskL)
861 |
862 | @staticmethod
863 | def multicastIpToMac(addr):
864 | # Compute the MAC address that we will use to send
865 | # packets out to. Multicast MACs are derived from
866 | # the multicast IP address.
867 | multicastMac = 0x01005e000000
868 | multicastMac |= PacketRelay.ip2long(addr) & 0x7fffff
869 | return struct.pack('!Q', multicastMac)[2:]
870 |
871 | @staticmethod
872 | def broadcastIpToMac(addr):
873 | broadcastMac = 0xffffffffffff
874 | return struct.pack('!Q', broadcastMac)[2:]
875 |
876 | @staticmethod
877 | def cidrToNetmask(bits):
878 | return socket.inet_ntoa(struct.pack('!I', (1 << 32) - (1 << (32 - bits))))
879 |
880 | class K8sCheck(http.server.BaseHTTPRequestHandler):
881 | def do_GET(self):
882 | self.send_response(200)
883 | self.send_header('Content-type', 'text/html')
884 | self.end_headers()
885 | self.wfile.write(bytes('OK', 'utf-8'))
886 |
887 | def main():
888 | parser = argparse.ArgumentParser()
889 | parser.add_argument('--interfaces', nargs='+', required=True,
890 | help='Relay between these interfaces (minimum 2).')
891 | parser.add_argument('--noTransmitInterfaces', nargs='+',
892 | help='Do not relay packets via these interfaces, listen only.')
893 | parser.add_argument('--ifFilter',
894 | help='JSON file specifying which interface(s) a particular source IP can relay to.')
895 | parser.add_argument('--ssdpUnicastAddr',
896 | help='IP address to listen to SSDP unicast replies, which will be'
897 | ' relayed to the IP that sent the SSDP multicast query.')
898 | parser.add_argument('--oneInterface', action='store_true',
899 | help='Slightly dangerous: only one interface exists, connected to two networks.')
900 | parser.add_argument('--relay', nargs='*',
901 | help='Relay additional multicast address(es).')
902 | parser.add_argument('--noMDNS', action='store_true',
903 | help='Do not relay mDNS packets.')
904 | parser.add_argument('--mdnsForceUnicast', action='store_true',
905 | help='Force mDNS packets to have the UNICAST-RESPONSE bit set.')
906 | parser.add_argument('--noSSDP', action='store_true',
907 | help='Do not relay SSDP packets.')
908 | parser.add_argument('--noSonosDiscovery', action='store_true',
909 | help='Do not relay broadcast Sonos discovery packets.')
910 | parser.add_argument('--homebrewNetifaces', action='store_true',
911 | help='Use self-contained netifaces-like package.')
912 | parser.add_argument('--ifNameStructLen', type=int, default=40,
913 | help='Help the self-contained netifaces work out its ifName struct length.')
914 | parser.add_argument('--allowNonEther', action='store_true',
915 | help='Allow non-ethernet interfaces to be configured.')
916 | parser.add_argument('--masquerade', nargs='+',
917 | help='Masquerade outbound packets from these interface(s).')
918 | parser.add_argument('--wait', action='store_true',
919 | help='Wait for IPv4 address assignment.')
920 | parser.add_argument('--ttl', type=int,
921 | help='Set TTL on outbound packets.')
922 | parser.add_argument('--listen', nargs='+',
923 | help='Listen for a remote connection from one or more remote addresses A.B.C.D.')
924 | parser.add_argument('--remote', nargs='+',
925 | help='Relay packets to remote multicast-relay(s) on A.B.C.D.')
926 | parser.add_argument('--remotePort', type=int, default=1900,
927 | help='Use this port to listen/connect to.')
928 | parser.add_argument('--remoteRetry', type=int, default=5,
929 | help='If the remote connection is terminated, retry at least N seconds later.')
930 | parser.add_argument('--noRemoteRelay', action='store_true',
931 | help='Only relay packets on local interfaces: don\'t relay packets out of --remote connected relays.')
932 | parser.add_argument('--aes',
933 | help='Encryption key for the connection to the remote multicast-relay.')
934 | parser.add_argument('--k8sport', type=int,
935 | help='Run k8s liveness/readiness server on the given port.')
936 | parser.add_argument('--foreground', action='store_true',
937 | help='Do not background.')
938 | parser.add_argument('--logfile',
939 | help='Save logs to this file.')
940 | parser.add_argument('--verbose', action='store_true',
941 | help='Enable verbose output.')
942 | args = parser.parse_args()
943 |
944 | if len(args.interfaces) < 2 and not args.oneInterface and not args.listen and not args.remote:
945 | print('You should specify at least two interfaces to relay between')
946 | return 1
947 |
948 | if args.remote and args.listen:
949 | print('Relay role should be either --listen or --remote (or neither) but not both')
950 | return 1
951 |
952 | if args.ttl and (args.ttl < 0 or args.ttl > 255):
953 | print('Invalid TTL (must be between 1 and 255)')
954 | return 1
955 |
956 | if not args.foreground:
957 | pid = os.fork()
958 | if pid != 0:
959 | return 0
960 | os.setsid()
961 | os.close(sys.stdin.fileno())
962 |
963 | logger = Logger(args.foreground, args.logfile, args.verbose)
964 |
965 | relays = set()
966 | if not args.noMDNS:
967 | relays.add(('%s:%d' % (PacketRelay.MDNS_MCAST_ADDR, PacketRelay.MDNS_MCAST_PORT), 'mDNS'))
968 | if not args.noSSDP:
969 | relays.add(('%s:%d' % (PacketRelay.SSDP_MCAST_ADDR, PacketRelay.SSDP_MCAST_PORT), 'SSDP'))
970 | if not args.noSonosDiscovery:
971 | relays.add(('%s:%d' % (PacketRelay.BROADCAST, 1900), 'Sonos Discovery'))
972 | relays.add(('%s:%d' % (PacketRelay.BROADCAST, 6969), 'Sonos Setup Discovery'))
973 |
974 | if args.ssdpUnicastAddr:
975 | relays.add(('%s:%d' % (args.ssdpUnicastAddr, PacketRelay.SSDP_UNICAST_PORT), 'SSDP Unicast'))
976 |
977 | if args.relay:
978 | for relay in args.relay:
979 | relays.add((relay, None))
980 |
981 | packetRelay = PacketRelay(interfaces = args.interfaces,
982 | noTransmitInterfaces = args.noTransmitInterfaces,
983 | ifFilter = args.ifFilter,
984 | waitForIP = args.wait,
985 | ttl = args.ttl,
986 | oneInterface = args.oneInterface,
987 | homebrewNetifaces = args.homebrewNetifaces,
988 | ifNameStructLen = args.ifNameStructLen,
989 | allowNonEther = args.allowNonEther,
990 | ssdpUnicastAddr = args.ssdpUnicastAddr,
991 | mdnsForceUnicast = args.mdnsForceUnicast,
992 | masquerade = args.masquerade,
993 | listen = args.listen,
994 | remote = args.remote,
995 | remotePort = args.remotePort,
996 | remoteRetry = args.remoteRetry,
997 | noRemoteRelay = args.noRemoteRelay,
998 | aes = args.aes,
999 | logger = logger)
1000 |
1001 | for relay in relays:
1002 | try:
1003 | (addr, port) = relay[0].split(':')
1004 | _ = PacketRelay.ip2long(addr)
1005 | port = int(port)
1006 | except:
1007 | errorMessage = '%s:%s: Expecting --relay A.B.C.D:P, where A.B.C.D is a multicast or broadcast IP address and P is a valid port number' % relay
1008 | if args.foreground:
1009 | print(errorMessage)
1010 | else:
1011 | logger.warning(errorMessage)
1012 | return 1
1013 |
1014 | if PacketRelay.isMulticast(addr):
1015 | relayType = 'multicast'
1016 | elif PacketRelay.isBroadcast(addr):
1017 | relayType = 'broadcast'
1018 | elif args.ssdpUnicastAddr:
1019 | relayType = 'unicast'
1020 | else:
1021 | errorMessage = 'IP address %s is neither a multicast nor a broadcast address' % addr
1022 | if args.foreground:
1023 | print(errorMessage)
1024 | else:
1025 | logger.warning(errorMessage)
1026 | return 1
1027 |
1028 | if port < 0 or port > 65535:
1029 | errorMessage = 'UDP port %s out of range' % port
1030 | if args.foreground:
1031 | print(errorMessage)
1032 | else:
1033 | logger.warning(errorMessage)
1034 | return 1
1035 |
1036 | logger.info('Adding %s relay for %s:%s%s' % (relayType, addr, port, relay[1] and ' (%s)' % relay[1] or ''))
1037 | packetRelay.addListener(addr, port, relay[1])
1038 |
1039 | if args.k8sport:
1040 | webServer = http.server.HTTPServer(('0.0.0.0', args.k8sport), K8sCheck)
1041 | threading.Thread(target=webServer.serve_forever, daemon=True).start()
1042 |
1043 | packetRelay.loop()
1044 |
1045 | if __name__ == '__main__':
1046 | sys.exit(main())
1047 |
1048 |
--------------------------------------------------------------------------------
/openwrt-python-encodings/__init__.py:
--------------------------------------------------------------------------------
1 | """ Standard "encodings" Package
2 |
3 | Standard Python encoding modules are stored in this package
4 | directory.
5 |
6 | Codec modules must have names corresponding to normalized encoding
7 | names as defined in the normalize_encoding() function below, e.g.
8 | 'utf-8' must be implemented by the module 'utf_8.py'.
9 |
10 | Each codec module must export the following interface:
11 |
12 | * getregentry() -> codecs.CodecInfo object
13 | The getregentry() API must a CodecInfo object with encoder, decoder,
14 | incrementalencoder, incrementaldecoder, streamwriter and streamreader
15 | atttributes which adhere to the Python Codec Interface Standard.
16 |
17 | In addition, a module may optionally also define the following
18 | APIs which are then used by the package's codec search function:
19 |
20 | * getaliases() -> sequence of encoding name strings to use as aliases
21 |
22 | Alias names returned by getaliases() must be normalized encoding
23 | names as defined by normalize_encoding().
24 |
25 | Written by Marc-Andre Lemburg (mal@lemburg.com).
26 |
27 | (c) Copyright CNRI, All Rights Reserved. NO WARRANTY.
28 |
29 | """#"
30 |
31 | import codecs
32 | from encodings import aliases
33 | import __builtin__
34 |
35 | _cache = {}
36 | _unknown = '--unknown--'
37 | _import_tail = ['*']
38 | _norm_encoding_map = (' . '
39 | '0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ '
40 | ' abcdefghijklmnopqrstuvwxyz '
41 | ' '
42 | ' '
43 | ' ')
44 | _aliases = aliases.aliases
45 |
46 | class CodecRegistryError(LookupError, SystemError):
47 | pass
48 |
49 | def normalize_encoding(encoding):
50 |
51 | """ Normalize an encoding name.
52 |
53 | Normalization works as follows: all non-alphanumeric
54 | characters except the dot used for Python package names are
55 | collapsed and replaced with a single underscore, e.g. ' -;#'
56 | becomes '_'. Leading and trailing underscores are removed.
57 |
58 | Note that encoding names should be ASCII only; if they do use
59 | non-ASCII characters, these must be Latin-1 compatible.
60 |
61 | """
62 | # Make sure we have an 8-bit string, because .translate() works
63 | # differently for Unicode strings.
64 | if hasattr(__builtin__, "unicode") and isinstance(encoding, unicode):
65 | # Note that .encode('latin-1') does *not* use the codec
66 | # registry, so this call doesn't recurse. (See unicodeobject.c
67 | # PyUnicode_AsEncodedString() for details)
68 | encoding = encoding.encode('latin-1')
69 | return '_'.join(encoding.translate(_norm_encoding_map).split())
70 |
71 | def search_function(encoding):
72 |
73 | # Cache lookup
74 | entry = _cache.get(encoding, _unknown)
75 | if entry is not _unknown:
76 | return entry
77 |
78 | # Import the module:
79 | #
80 | # First try to find an alias for the normalized encoding
81 | # name and lookup the module using the aliased name, then try to
82 | # lookup the module using the standard import scheme, i.e. first
83 | # try in the encodings package, then at top-level.
84 | #
85 | norm_encoding = normalize_encoding(encoding)
86 | aliased_encoding = _aliases.get(norm_encoding) or \
87 | _aliases.get(norm_encoding.replace('.', '_'))
88 | if aliased_encoding is not None:
89 | modnames = [aliased_encoding,
90 | norm_encoding]
91 | else:
92 | modnames = [norm_encoding]
93 | for modname in modnames:
94 | if not modname or '.' in modname:
95 | continue
96 | try:
97 | # Import is absolute to prevent the possibly malicious import of a
98 | # module with side-effects that is not in the 'encodings' package.
99 | mod = __import__('encodings.' + modname, fromlist=_import_tail,
100 | level=0)
101 | except ImportError:
102 | pass
103 | else:
104 | break
105 | else:
106 | mod = None
107 |
108 | try:
109 | getregentry = mod.getregentry
110 | except AttributeError:
111 | # Not a codec module
112 | mod = None
113 |
114 | if mod is None:
115 | # Cache misses
116 | _cache[encoding] = None
117 | return None
118 |
119 | # Now ask the module for the registry entry
120 | entry = getregentry()
121 | if not isinstance(entry, codecs.CodecInfo):
122 | if not 4 <= len(entry) <= 7:
123 | raise CodecRegistryError,\
124 | 'module "%s" (%s) failed to register' % \
125 | (mod.__name__, mod.__file__)
126 | if not hasattr(entry[0], '__call__') or \
127 | not hasattr(entry[1], '__call__') or \
128 | (entry[2] is not None and not hasattr(entry[2], '__call__')) or \
129 | (entry[3] is not None and not hasattr(entry[3], '__call__')) or \
130 | (len(entry) > 4 and entry[4] is not None and not hasattr(entry[4], '__call__')) or \
131 | (len(entry) > 5 and entry[5] is not None and not hasattr(entry[5], '__call__')):
132 | raise CodecRegistryError,\
133 | 'incompatible codecs in module "%s" (%s)' % \
134 | (mod.__name__, mod.__file__)
135 | if len(entry)<7 or entry[6] is None:
136 | entry += (None,)*(6-len(entry)) + (mod.__name__.split(".", 1)[1],)
137 | entry = codecs.CodecInfo(*entry)
138 |
139 | # Cache the codec registry entry
140 | _cache[encoding] = entry
141 |
142 | # Register its aliases (without overwriting previously registered
143 | # aliases)
144 | try:
145 | codecaliases = mod.getaliases()
146 | except AttributeError:
147 | pass
148 | else:
149 | for alias in codecaliases:
150 | if alias not in _aliases:
151 | _aliases[alias] = modname
152 |
153 | # Return the registry entry
154 | return entry
155 |
156 | # Register the search_function in the Python codec registry
157 | codecs.register(search_function)
158 |
--------------------------------------------------------------------------------
/openwrt-python-encodings/aliases.py:
--------------------------------------------------------------------------------
1 | """ Encoding Aliases Support
2 |
3 | This module is used by the encodings package search function to
4 | map encodings names to module names.
5 |
6 | Note that the search function normalizes the encoding names before
7 | doing the lookup, so the mapping will have to map normalized
8 | encoding names to module names.
9 |
10 | Contents:
11 |
12 | The following aliases dictionary contains mappings of all IANA
13 | character set names for which the Python core library provides
14 | codecs. In addition to these, a few Python specific codec
15 | aliases have also been added.
16 |
17 | """
18 | aliases = {
19 |
20 | # Please keep this list sorted alphabetically by value !
21 |
22 | # ascii codec
23 | '646' : 'ascii',
24 | 'ansi_x3.4_1968' : 'ascii',
25 | 'ansi_x3_4_1968' : 'ascii', # some email headers use this non-standard name
26 | 'ansi_x3.4_1986' : 'ascii',
27 | 'cp367' : 'ascii',
28 | 'csascii' : 'ascii',
29 | 'ibm367' : 'ascii',
30 | 'iso646_us' : 'ascii',
31 | 'iso_646.irv_1991' : 'ascii',
32 | 'iso_ir_6' : 'ascii',
33 | 'us' : 'ascii',
34 | 'us_ascii' : 'ascii',
35 |
36 | # base64_codec codec
37 | 'base64' : 'base64_codec',
38 | 'base_64' : 'base64_codec',
39 |
40 | # big5 codec
41 | 'big5_tw' : 'big5',
42 | 'csbig5' : 'big5',
43 |
44 | # big5hkscs codec
45 | 'big5_hkscs' : 'big5hkscs',
46 | 'hkscs' : 'big5hkscs',
47 |
48 | # bz2_codec codec
49 | 'bz2' : 'bz2_codec',
50 |
51 | # cp037 codec
52 | '037' : 'cp037',
53 | 'csibm037' : 'cp037',
54 | 'ebcdic_cp_ca' : 'cp037',
55 | 'ebcdic_cp_nl' : 'cp037',
56 | 'ebcdic_cp_us' : 'cp037',
57 | 'ebcdic_cp_wt' : 'cp037',
58 | 'ibm037' : 'cp037',
59 | 'ibm039' : 'cp037',
60 |
61 | # cp1026 codec
62 | '1026' : 'cp1026',
63 | 'csibm1026' : 'cp1026',
64 | 'ibm1026' : 'cp1026',
65 |
66 | # cp1140 codec
67 | '1140' : 'cp1140',
68 | 'ibm1140' : 'cp1140',
69 |
70 | # cp1250 codec
71 | '1250' : 'cp1250',
72 | 'windows_1250' : 'cp1250',
73 |
74 | # cp1251 codec
75 | '1251' : 'cp1251',
76 | 'windows_1251' : 'cp1251',
77 |
78 | # cp1252 codec
79 | '1252' : 'cp1252',
80 | 'windows_1252' : 'cp1252',
81 |
82 | # cp1253 codec
83 | '1253' : 'cp1253',
84 | 'windows_1253' : 'cp1253',
85 |
86 | # cp1254 codec
87 | '1254' : 'cp1254',
88 | 'windows_1254' : 'cp1254',
89 |
90 | # cp1255 codec
91 | '1255' : 'cp1255',
92 | 'windows_1255' : 'cp1255',
93 |
94 | # cp1256 codec
95 | '1256' : 'cp1256',
96 | 'windows_1256' : 'cp1256',
97 |
98 | # cp1257 codec
99 | '1257' : 'cp1257',
100 | 'windows_1257' : 'cp1257',
101 |
102 | # cp1258 codec
103 | '1258' : 'cp1258',
104 | 'windows_1258' : 'cp1258',
105 |
106 | # cp424 codec
107 | '424' : 'cp424',
108 | 'csibm424' : 'cp424',
109 | 'ebcdic_cp_he' : 'cp424',
110 | 'ibm424' : 'cp424',
111 |
112 | # cp437 codec
113 | '437' : 'cp437',
114 | 'cspc8codepage437' : 'cp437',
115 | 'ibm437' : 'cp437',
116 |
117 | # cp500 codec
118 | '500' : 'cp500',
119 | 'csibm500' : 'cp500',
120 | 'ebcdic_cp_be' : 'cp500',
121 | 'ebcdic_cp_ch' : 'cp500',
122 | 'ibm500' : 'cp500',
123 |
124 | # cp775 codec
125 | '775' : 'cp775',
126 | 'cspc775baltic' : 'cp775',
127 | 'ibm775' : 'cp775',
128 |
129 | # cp850 codec
130 | '850' : 'cp850',
131 | 'cspc850multilingual' : 'cp850',
132 | 'ibm850' : 'cp850',
133 |
134 | # cp852 codec
135 | '852' : 'cp852',
136 | 'cspcp852' : 'cp852',
137 | 'ibm852' : 'cp852',
138 |
139 | # cp855 codec
140 | '855' : 'cp855',
141 | 'csibm855' : 'cp855',
142 | 'ibm855' : 'cp855',
143 |
144 | # cp857 codec
145 | '857' : 'cp857',
146 | 'csibm857' : 'cp857',
147 | 'ibm857' : 'cp857',
148 |
149 | # cp858 codec
150 | '858' : 'cp858',
151 | 'csibm858' : 'cp858',
152 | 'ibm858' : 'cp858',
153 |
154 | # cp860 codec
155 | '860' : 'cp860',
156 | 'csibm860' : 'cp860',
157 | 'ibm860' : 'cp860',
158 |
159 | # cp861 codec
160 | '861' : 'cp861',
161 | 'cp_is' : 'cp861',
162 | 'csibm861' : 'cp861',
163 | 'ibm861' : 'cp861',
164 |
165 | # cp862 codec
166 | '862' : 'cp862',
167 | 'cspc862latinhebrew' : 'cp862',
168 | 'ibm862' : 'cp862',
169 |
170 | # cp863 codec
171 | '863' : 'cp863',
172 | 'csibm863' : 'cp863',
173 | 'ibm863' : 'cp863',
174 |
175 | # cp864 codec
176 | '864' : 'cp864',
177 | 'csibm864' : 'cp864',
178 | 'ibm864' : 'cp864',
179 |
180 | # cp865 codec
181 | '865' : 'cp865',
182 | 'csibm865' : 'cp865',
183 | 'ibm865' : 'cp865',
184 |
185 | # cp866 codec
186 | '866' : 'cp866',
187 | 'csibm866' : 'cp866',
188 | 'ibm866' : 'cp866',
189 |
190 | # cp869 codec
191 | '869' : 'cp869',
192 | 'cp_gr' : 'cp869',
193 | 'csibm869' : 'cp869',
194 | 'ibm869' : 'cp869',
195 |
196 | # cp932 codec
197 | '932' : 'cp932',
198 | 'ms932' : 'cp932',
199 | 'mskanji' : 'cp932',
200 | 'ms_kanji' : 'cp932',
201 |
202 | # cp949 codec
203 | '949' : 'cp949',
204 | 'ms949' : 'cp949',
205 | 'uhc' : 'cp949',
206 |
207 | # cp950 codec
208 | '950' : 'cp950',
209 | 'ms950' : 'cp950',
210 |
211 | # euc_jis_2004 codec
212 | 'jisx0213' : 'euc_jis_2004',
213 | 'eucjis2004' : 'euc_jis_2004',
214 | 'euc_jis2004' : 'euc_jis_2004',
215 |
216 | # euc_jisx0213 codec
217 | 'eucjisx0213' : 'euc_jisx0213',
218 |
219 | # euc_jp codec
220 | 'eucjp' : 'euc_jp',
221 | 'ujis' : 'euc_jp',
222 | 'u_jis' : 'euc_jp',
223 |
224 | # euc_kr codec
225 | 'euckr' : 'euc_kr',
226 | 'korean' : 'euc_kr',
227 | 'ksc5601' : 'euc_kr',
228 | 'ks_c_5601' : 'euc_kr',
229 | 'ks_c_5601_1987' : 'euc_kr',
230 | 'ksx1001' : 'euc_kr',
231 | 'ks_x_1001' : 'euc_kr',
232 |
233 | # gb18030 codec
234 | 'gb18030_2000' : 'gb18030',
235 |
236 | # gb2312 codec
237 | 'chinese' : 'gb2312',
238 | 'csiso58gb231280' : 'gb2312',
239 | 'euc_cn' : 'gb2312',
240 | 'euccn' : 'gb2312',
241 | 'eucgb2312_cn' : 'gb2312',
242 | 'gb2312_1980' : 'gb2312',
243 | 'gb2312_80' : 'gb2312',
244 | 'iso_ir_58' : 'gb2312',
245 |
246 | # gbk codec
247 | '936' : 'gbk',
248 | 'cp936' : 'gbk',
249 | 'ms936' : 'gbk',
250 |
251 | # hex_codec codec
252 | 'hex' : 'hex_codec',
253 |
254 | # hp_roman8 codec
255 | 'roman8' : 'hp_roman8',
256 | 'r8' : 'hp_roman8',
257 | 'csHPRoman8' : 'hp_roman8',
258 |
259 | # hz codec
260 | 'hzgb' : 'hz',
261 | 'hz_gb' : 'hz',
262 | 'hz_gb_2312' : 'hz',
263 |
264 | # iso2022_jp codec
265 | 'csiso2022jp' : 'iso2022_jp',
266 | 'iso2022jp' : 'iso2022_jp',
267 | 'iso_2022_jp' : 'iso2022_jp',
268 |
269 | # iso2022_jp_1 codec
270 | 'iso2022jp_1' : 'iso2022_jp_1',
271 | 'iso_2022_jp_1' : 'iso2022_jp_1',
272 |
273 | # iso2022_jp_2 codec
274 | 'iso2022jp_2' : 'iso2022_jp_2',
275 | 'iso_2022_jp_2' : 'iso2022_jp_2',
276 |
277 | # iso2022_jp_2004 codec
278 | 'iso_2022_jp_2004' : 'iso2022_jp_2004',
279 | 'iso2022jp_2004' : 'iso2022_jp_2004',
280 |
281 | # iso2022_jp_3 codec
282 | 'iso2022jp_3' : 'iso2022_jp_3',
283 | 'iso_2022_jp_3' : 'iso2022_jp_3',
284 |
285 | # iso2022_jp_ext codec
286 | 'iso2022jp_ext' : 'iso2022_jp_ext',
287 | 'iso_2022_jp_ext' : 'iso2022_jp_ext',
288 |
289 | # iso2022_kr codec
290 | 'csiso2022kr' : 'iso2022_kr',
291 | 'iso2022kr' : 'iso2022_kr',
292 | 'iso_2022_kr' : 'iso2022_kr',
293 |
294 | # iso8859_10 codec
295 | 'csisolatin6' : 'iso8859_10',
296 | 'iso_8859_10' : 'iso8859_10',
297 | 'iso_8859_10_1992' : 'iso8859_10',
298 | 'iso_ir_157' : 'iso8859_10',
299 | 'l6' : 'iso8859_10',
300 | 'latin6' : 'iso8859_10',
301 |
302 | # iso8859_11 codec
303 | 'thai' : 'iso8859_11',
304 | 'iso_8859_11' : 'iso8859_11',
305 | 'iso_8859_11_2001' : 'iso8859_11',
306 |
307 | # iso8859_13 codec
308 | 'iso_8859_13' : 'iso8859_13',
309 | 'l7' : 'iso8859_13',
310 | 'latin7' : 'iso8859_13',
311 |
312 | # iso8859_14 codec
313 | 'iso_8859_14' : 'iso8859_14',
314 | 'iso_8859_14_1998' : 'iso8859_14',
315 | 'iso_celtic' : 'iso8859_14',
316 | 'iso_ir_199' : 'iso8859_14',
317 | 'l8' : 'iso8859_14',
318 | 'latin8' : 'iso8859_14',
319 |
320 | # iso8859_15 codec
321 | 'iso_8859_15' : 'iso8859_15',
322 | 'l9' : 'iso8859_15',
323 | 'latin9' : 'iso8859_15',
324 |
325 | # iso8859_16 codec
326 | 'iso_8859_16' : 'iso8859_16',
327 | 'iso_8859_16_2001' : 'iso8859_16',
328 | 'iso_ir_226' : 'iso8859_16',
329 | 'l10' : 'iso8859_16',
330 | 'latin10' : 'iso8859_16',
331 |
332 | # iso8859_2 codec
333 | 'csisolatin2' : 'iso8859_2',
334 | 'iso_8859_2' : 'iso8859_2',
335 | 'iso_8859_2_1987' : 'iso8859_2',
336 | 'iso_ir_101' : 'iso8859_2',
337 | 'l2' : 'iso8859_2',
338 | 'latin2' : 'iso8859_2',
339 |
340 | # iso8859_3 codec
341 | 'csisolatin3' : 'iso8859_3',
342 | 'iso_8859_3' : 'iso8859_3',
343 | 'iso_8859_3_1988' : 'iso8859_3',
344 | 'iso_ir_109' : 'iso8859_3',
345 | 'l3' : 'iso8859_3',
346 | 'latin3' : 'iso8859_3',
347 |
348 | # iso8859_4 codec
349 | 'csisolatin4' : 'iso8859_4',
350 | 'iso_8859_4' : 'iso8859_4',
351 | 'iso_8859_4_1988' : 'iso8859_4',
352 | 'iso_ir_110' : 'iso8859_4',
353 | 'l4' : 'iso8859_4',
354 | 'latin4' : 'iso8859_4',
355 |
356 | # iso8859_5 codec
357 | 'csisolatincyrillic' : 'iso8859_5',
358 | 'cyrillic' : 'iso8859_5',
359 | 'iso_8859_5' : 'iso8859_5',
360 | 'iso_8859_5_1988' : 'iso8859_5',
361 | 'iso_ir_144' : 'iso8859_5',
362 |
363 | # iso8859_6 codec
364 | 'arabic' : 'iso8859_6',
365 | 'asmo_708' : 'iso8859_6',
366 | 'csisolatinarabic' : 'iso8859_6',
367 | 'ecma_114' : 'iso8859_6',
368 | 'iso_8859_6' : 'iso8859_6',
369 | 'iso_8859_6_1987' : 'iso8859_6',
370 | 'iso_ir_127' : 'iso8859_6',
371 |
372 | # iso8859_7 codec
373 | 'csisolatingreek' : 'iso8859_7',
374 | 'ecma_118' : 'iso8859_7',
375 | 'elot_928' : 'iso8859_7',
376 | 'greek' : 'iso8859_7',
377 | 'greek8' : 'iso8859_7',
378 | 'iso_8859_7' : 'iso8859_7',
379 | 'iso_8859_7_1987' : 'iso8859_7',
380 | 'iso_ir_126' : 'iso8859_7',
381 |
382 | # iso8859_8 codec
383 | 'csisolatinhebrew' : 'iso8859_8',
384 | 'hebrew' : 'iso8859_8',
385 | 'iso_8859_8' : 'iso8859_8',
386 | 'iso_8859_8_1988' : 'iso8859_8',
387 | 'iso_ir_138' : 'iso8859_8',
388 |
389 | # iso8859_9 codec
390 | 'csisolatin5' : 'iso8859_9',
391 | 'iso_8859_9' : 'iso8859_9',
392 | 'iso_8859_9_1989' : 'iso8859_9',
393 | 'iso_ir_148' : 'iso8859_9',
394 | 'l5' : 'iso8859_9',
395 | 'latin5' : 'iso8859_9',
396 |
397 | # johab codec
398 | 'cp1361' : 'johab',
399 | 'ms1361' : 'johab',
400 |
401 | # koi8_r codec
402 | 'cskoi8r' : 'koi8_r',
403 |
404 | # latin_1 codec
405 | #
406 | # Note that the latin_1 codec is implemented internally in C and a
407 | # lot faster than the charmap codec iso8859_1 which uses the same
408 | # encoding. This is why we discourage the use of the iso8859_1
409 | # codec and alias it to latin_1 instead.
410 | #
411 | '8859' : 'latin_1',
412 | 'cp819' : 'latin_1',
413 | 'csisolatin1' : 'latin_1',
414 | 'ibm819' : 'latin_1',
415 | 'iso8859' : 'latin_1',
416 | 'iso8859_1' : 'latin_1',
417 | 'iso_8859_1' : 'latin_1',
418 | 'iso_8859_1_1987' : 'latin_1',
419 | 'iso_ir_100' : 'latin_1',
420 | 'l1' : 'latin_1',
421 | 'latin' : 'latin_1',
422 | 'latin1' : 'latin_1',
423 |
424 | # mac_cyrillic codec
425 | 'maccyrillic' : 'mac_cyrillic',
426 |
427 | # mac_greek codec
428 | 'macgreek' : 'mac_greek',
429 |
430 | # mac_iceland codec
431 | 'maciceland' : 'mac_iceland',
432 |
433 | # mac_latin2 codec
434 | 'maccentraleurope' : 'mac_latin2',
435 | 'maclatin2' : 'mac_latin2',
436 |
437 | # mac_roman codec
438 | 'macroman' : 'mac_roman',
439 |
440 | # mac_turkish codec
441 | 'macturkish' : 'mac_turkish',
442 |
443 | # mbcs codec
444 | 'dbcs' : 'mbcs',
445 |
446 | # ptcp154 codec
447 | 'csptcp154' : 'ptcp154',
448 | 'pt154' : 'ptcp154',
449 | 'cp154' : 'ptcp154',
450 | 'cyrillic_asian' : 'ptcp154',
451 |
452 | # quopri_codec codec
453 | 'quopri' : 'quopri_codec',
454 | 'quoted_printable' : 'quopri_codec',
455 | 'quotedprintable' : 'quopri_codec',
456 |
457 | # rot_13 codec
458 | 'rot13' : 'rot_13',
459 |
460 | # shift_jis codec
461 | 'csshiftjis' : 'shift_jis',
462 | 'shiftjis' : 'shift_jis',
463 | 'sjis' : 'shift_jis',
464 | 's_jis' : 'shift_jis',
465 |
466 | # shift_jis_2004 codec
467 | 'shiftjis2004' : 'shift_jis_2004',
468 | 'sjis_2004' : 'shift_jis_2004',
469 | 's_jis_2004' : 'shift_jis_2004',
470 |
471 | # shift_jisx0213 codec
472 | 'shiftjisx0213' : 'shift_jisx0213',
473 | 'sjisx0213' : 'shift_jisx0213',
474 | 's_jisx0213' : 'shift_jisx0213',
475 |
476 | # tactis codec
477 | 'tis260' : 'tactis',
478 |
479 | # tis_620 codec
480 | 'tis620' : 'tis_620',
481 | 'tis_620_0' : 'tis_620',
482 | 'tis_620_2529_0' : 'tis_620',
483 | 'tis_620_2529_1' : 'tis_620',
484 | 'iso_ir_166' : 'tis_620',
485 |
486 | # utf_16 codec
487 | 'u16' : 'utf_16',
488 | 'utf16' : 'utf_16',
489 |
490 | # utf_16_be codec
491 | 'unicodebigunmarked' : 'utf_16_be',
492 | 'utf_16be' : 'utf_16_be',
493 |
494 | # utf_16_le codec
495 | 'unicodelittleunmarked' : 'utf_16_le',
496 | 'utf_16le' : 'utf_16_le',
497 |
498 | # utf_32 codec
499 | 'u32' : 'utf_32',
500 | 'utf32' : 'utf_32',
501 |
502 | # utf_32_be codec
503 | 'utf_32be' : 'utf_32_be',
504 |
505 | # utf_32_le codec
506 | 'utf_32le' : 'utf_32_le',
507 |
508 | # utf_7 codec
509 | 'u7' : 'utf_7',
510 | 'utf7' : 'utf_7',
511 | 'unicode_1_1_utf_7' : 'utf_7',
512 |
513 | # utf_8 codec
514 | 'u8' : 'utf_8',
515 | 'utf' : 'utf_8',
516 | 'utf8' : 'utf_8',
517 | 'utf8_ucs2' : 'utf_8',
518 | 'utf8_ucs4' : 'utf_8',
519 |
520 | # uu_codec codec
521 | 'uu' : 'uu_codec',
522 |
523 | # zlib_codec codec
524 | 'zip' : 'zlib_codec',
525 | 'zlib' : 'zlib_codec',
526 |
527 | }
528 |
--------------------------------------------------------------------------------
/openwrt-python-encodings/ascii.py:
--------------------------------------------------------------------------------
1 | """ Python 'ascii' Codec
2 |
3 |
4 | Written by Marc-Andre Lemburg (mal@lemburg.com).
5 |
6 | (c) Copyright CNRI, All Rights Reserved. NO WARRANTY.
7 |
8 | """
9 | import codecs
10 |
11 | ### Codec APIs
12 |
13 | class Codec(codecs.Codec):
14 |
15 | # Note: Binding these as C functions will result in the class not
16 | # converting them to methods. This is intended.
17 | encode = codecs.ascii_encode
18 | decode = codecs.ascii_decode
19 |
20 | class IncrementalEncoder(codecs.IncrementalEncoder):
21 | def encode(self, input, final=False):
22 | return codecs.ascii_encode(input, self.errors)[0]
23 |
24 | class IncrementalDecoder(codecs.IncrementalDecoder):
25 | def decode(self, input, final=False):
26 | return codecs.ascii_decode(input, self.errors)[0]
27 |
28 | class StreamWriter(Codec,codecs.StreamWriter):
29 | pass
30 |
31 | class StreamReader(Codec,codecs.StreamReader):
32 | pass
33 |
34 | class StreamConverter(StreamWriter,StreamReader):
35 |
36 | encode = codecs.ascii_decode
37 | decode = codecs.ascii_encode
38 |
39 | ### encodings module API
40 |
41 | def getregentry():
42 | return codecs.CodecInfo(
43 | name='ascii',
44 | encode=Codec.encode,
45 | decode=Codec.decode,
46 | incrementalencoder=IncrementalEncoder,
47 | incrementaldecoder=IncrementalDecoder,
48 | streamwriter=StreamWriter,
49 | streamreader=StreamReader,
50 | )
51 |
--------------------------------------------------------------------------------
/ssdpDiscover.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import socket
5 | import sys
6 |
7 | """
8 | Simple script to send a SSDP M-SEARCH message out on a given interface.
9 | """
10 |
11 | def main():
12 | parser = argparse.ArgumentParser()
13 | parser.add_argument('--ifAddr', help='Send out on the interface with the given address.')
14 | args = parser.parse_args()
15 |
16 | msearch = 'M-SEARCH * HTTP/1.1\r\n' \
17 | 'HOST:239.255.255.250:1900\r\n' \
18 | 'ST:upnp:rootdevice\r\n' \
19 | 'MX:2\r\n' \
20 | 'MAN:"ssdp:discover"\r\n' \
21 | '\r\n'
22 |
23 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
24 | if args.ifAddr:
25 | s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(args.ifAddr))
26 |
27 | s.settimeout(2)
28 | s.sendto(msearch.encode('utf-8'), ('239.255.255.250', 1900))
29 |
30 | try:
31 | while True:
32 | data, addr = s.recvfrom(65535)
33 | try:
34 | print('%s [%s]' % (socket.gethostbyaddr(addr[0])[0], addr[0]))
35 | except socket.herror:
36 | print(addr[0])
37 | print(data)
38 | except socket.timeout:
39 | pass
40 |
41 | if __name__ == '__main__':
42 | sys.exit(main())
43 |
44 |
--------------------------------------------------------------------------------
/test_PacketRelay.py:
--------------------------------------------------------------------------------
1 | #
2 | # unit tests using the py.test framework
3 | #
4 | # Dependency: python-pytest python-pytest-cov
5 | # Run as: /usr/bin/pytest --cov=.
6 | #
7 | # Note: examples of packets and checksums can be found using wireshark.
8 | # You can then right-click and Copy->...As Escaped string.
9 |
10 | import struct
11 | # We can't use import directly because of the hyphen in the file name,
12 | # so we do this via importlib
13 | import importlib
14 | mr = importlib.import_module('multicast-relay')
15 |
16 |
17 | def test_net_checksum_ipv4():
18 | ipv4_header_tests = (
19 | # IGMPv2 multicast to 224.0.0.1:
20 | (0x9fda, "\x46\xc0\x00\x20\xe2\x92\x00\x00\x01\x02\x9f\xda\xc0\xa8" \
21 | "\x01\x01\xe0\x00\x00\x01\x94\x04\x00\x00"),
22 | # 216.58.198.174 -> 192.168.1.196 unicast tcp:
23 | (0xffba, "\x45\x00\x00\x34\xe0\xb3\x00\x00\x79\x06\xff\xba\xd8\x3a" \
24 | "\xc6\xae\xc0\xa8\x01\xc4"),
25 | # 192.168.1.1 -> 192.168.1.196 UDP DNS response
26 | (0xc000, "\x45\x00\x00\xb4\xf6\x22\x40\x00\x40\x11\xc0\x00\xc0\xa8" \
27 | "\x01\x01\xc0\xa8\x01\xc4"),
28 | )
29 |
30 | for (cksum, ip_header) in ipv4_header_tests:
31 | assert cksum == mr.PacketRelay.net_checksum(ip_header[:10]
32 | + struct.pack('!H', 0)
33 | + ip_header[12:])
34 | assert 0 == mr.PacketRelay.net_checksum(ip_header)
35 |
36 |
37 | def test_net_checksum_udp():
38 | # UDP pseudo header (used to calculate the checksum):
39 | # src_ip, dst_ip, 0, protocol, udp_length, udp_header, data
40 | udp_pseudo_header_tests = (
41 | # NTP v4, client:
42 | (0x101f, "\xc0\xa8\x01\xc4" "\x5b\xbd\x5b\x9d" "\x00" "\x11" "\x00\x38" \
43 | "\xd7\x6e\x00\x7b\x00\x38\x10\x1f" \
44 | "\x23\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
45 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
46 | "\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x01\x7b\x46\x34\x2f\xeb\x36"),
47 | # NTP v4, server
48 | (0x0d66, "\x5b\xbd\x5b\x9d" "\xc0\xa8\x01\xc4" "\x00" "\x11" "\x00\x38" \
49 | "\x00\x7b\xd5\x1e\x00\x38\x0d\x66" \
50 | "\x24\x02\x03\xe8\x00\x00\x08\x29\x00\x00\x09\xab\x84\xf6\x0b\xe7" \
51 | "\xe0\x01\x76\xa5\x62\xb6\x0f\x58\xe0\x01\x7c\x07\x16\x61\x28\xc4" \
52 | "\xe0\x01\x7c\x07\x53\xd6\x52\x7d\xe0\x01\x7c\x07\x53\xda\xc1\xf6"),
53 | # DNS standard query:
54 | (0x874e, "\xc0\xa8\x01\xc4" "\xc0\xa8\x01\x01" "\x00" "\x11" "\x00\x4e" \
55 | "\xad\xa5\x00\x35\x00\x4e\x87\x4e" \
56 | "\xae\x06\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x0d\x6e\x31\x2d" \
57 | "\x32\x31\x31\x36\x39\x37\x33\x36\x34\x35\x09\x65\x75\x2d\x77\x65" \
58 | "\x73\x74\x2d\x31\x03\x65\x6c\x62\x09\x61\x6d\x61\x7a\x6f\x6e\x61" \
59 | "\x77\x73\x03\x63\x6f\x6d\x00\x00\x01\x00\x01\x00\x00\x29\x02\x00" \
60 | "\x00\x00\x00\x00\x00\x00"),
61 | # DNS standard query response
62 | (0x73ad, "\xc0\xa8\x01\x01" "\xc0\xa8\x01\xc4" "\x00" "\x11" "\x00\x44" \
63 | "\x00\x35\x9f\xe2\x00\x44\x73\xad"
64 | "\x22\x97\x81\x80\x00\x01\x00\x01\x00\x00\x00\x01\x04\x64\x6f\x63" \
65 | "\x73\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00" \
66 | "\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x78\x00\x04\xd8\x3a\xd4" \
67 | "\x4e\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00"),
68 | # DNS standard query response - with one less padding to test odd size scenario:
69 | (0x73ad, "\xc0\xa8\x01\x01" "\xc0\xa8\x01\xc4" "\x00" "\x11" "\x00\x44" \
70 | "\x00\x35\x9f\xe2\x00\x44\x73\xad"
71 | "\x22\x97\x81\x80\x00\x01\x00\x01\x00\x00\x00\x01\x04\x64\x6f\x63" \
72 | "\x73\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00" \
73 | "\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x78\x00\x04\xd8\x3a\xd4" \
74 | "\x4e\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00"),
75 | )
76 |
77 | for (cksum, udp_header) in udp_pseudo_header_tests:
78 | assert cksum == mr.PacketRelay.net_checksum(udp_header[:18] + struct.pack('!H', 0) + udp_header[20:])
79 | assert 0 == mr.PacketRelay.net_checksum(udp_header)
80 |
81 |
82 | def test_unicast_ip2mac_str():
83 | arp = """IP address HW type Flags HW address Mask Device
84 | 192.168.0.1 0x1 0x2 30:65:EC:6F:C4:58 * eth0
85 | 192.168.0.2 0x1 0x2 30:65:ec:6f:c4:59 * eth0"""
86 |
87 | assert mr.PacketRelay.unicast_ip2mac_str("192.168.0.1", proc_net_arp_content=arp) == "30:65:EC:6F:C4:58"
88 | assert mr.PacketRelay.unicast_ip2mac_str("192.168.0.2", proc_net_arp_content=arp) == "30:65:ec:6f:c4:59"
89 | assert mr.PacketRelay.unicast_ip2mac_str("172.16.5.1", proc_net_arp_content=arp) is None
90 |
91 |
92 | def test_modify_udp_packet():
93 | # modify_udp_packet(data, ipHeaderLength, newSrcAddr=None,
94 | # newSrcPort=None, newDstAddr=None, newDstPort=None)
95 |
96 | # DNS query:
97 | udp_packet = \
98 | "\x45\x00" \
99 | "\x00\x54\x33\x91\x40\x00\x40\x11\x84\x93\xc0\xa8\x00\x26\xc0\xa8" \
100 | "\x00\xfe\xdf\xdd\x00\x35\x00\x40\xfe\x24\x72\xb7\x01\x00\x00\x01" \
101 | "\x00\x00\x00\x00\x00\x01\x09\x67\x6f\x6f\x67\x6c\x65\x61\x64\x73" \
102 | "\x01\x67\x0b\x64\x6f\x75\x62\x6c\x65\x63\x6c\x69\x63\x6b\x03\x6e" \
103 | "\x65\x74\x00\x00\x01\x00\x01\x00\x00\x29\x02\x00\x00\x00\x00\x00" \
104 | "\x00\x00"
105 |
106 | # Make sure the packet is reconstructed the same if we don't change anything:
107 | assert mr.PacketRelay.modify_udp_packet(udp_packet, 20) == udp_packet
108 |
109 | # Now change IPs and ports:
110 | modified_packet = \
111 | "\x45\x00" \
112 | "\x00\x54\x33\x91\x40\x00\x40\x11\x8d\x41\xc0\xa8\x01\xc4\x5b\xbd" \
113 | "\x5b\x9d\xde\xde\x01\x34\x00\x40\x06\xd3\x72\xb7\x01\x00\x00\x01" \
114 | "\x00\x00\x00\x00\x00\x01\x09\x67\x6f\x6f\x67\x6c\x65\x61\x64\x73" \
115 | "\x01\x67\x0b\x64\x6f\x75\x62\x6c\x65\x63\x6c\x69\x63\x6b\x03\x6e" \
116 | "\x65\x74\x00\x00\x01\x00\x01\x00\x00\x29\x02\x00\x00\x00\x00\x00" \
117 | "\x00\x00"
118 |
119 | assert mr.PacketRelay.modify_udp_packet(udp_packet, 20, '192.168.1.196',
120 | 57054, '91.189.91.157', 308) == modified_packet
121 |
122 | # SSDP multicast query:
123 | udp_packet = \
124 | "\x45\x00" \
125 | "\x00\x99\x4a\x5f\x40\x00\x01\x11\x7c\xe2\xc0\xa8\x01\x70\xef\xff" \
126 | "\xff\xfa\xd9\x79\x07\x6c\x00\x85\x3e\x62\x4d\x2d\x53\x45\x41\x52" \
127 | "\x43\x48\x20\x2a\x20\x48\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a\x48" \
128 | "\x4f\x53\x54\x3a\x20\x32\x33\x39\x2e\x32\x35\x35\x2e\x32\x35\x35" \
129 | "\x2e\x32\x35\x30\x3a\x31\x39\x30\x30\x0d\x0a\x4d\x41\x4e\x3a\x20" \
130 | "\x22\x73\x73\x64\x70\x3a\x64\x69\x73\x63\x6f\x76\x65\x72\x22\x0d" \
131 | "\x0a\x4d\x58\x3a\x20\x31\x0d\x0a\x53\x54\x3a\x20\x75\x72\x6e\x3a" \
132 | "\x64\x69\x61\x6c\x2d\x6d\x75\x6c\x74\x69\x73\x63\x72\x65\x65\x6e" \
133 | "\x2d\x6f\x72\x67\x3a\x73\x65\x72\x76\x69\x63\x65\x3a\x64\x69\x61" \
134 | "\x6c\x3a\x31\x0d\x0a\x0d\x0a"
135 |
136 | # Make sure the packet is reconstructed the same if we don't change anything:
137 | assert mr.PacketRelay.modify_udp_packet(udp_packet, 20) == udp_packet
138 |
139 | # Now change IPs and ports:
140 | modified_packet = \
141 | "\x45\x00" \
142 | "\x00\x99\x4a\x5f\x40\x00\x01\x11\x34\xfa\x0a\x00\x00\x01\xef\xff" \
143 | "\xff\xfa\x07\x6d\x07\x6c\x00\x85\xc8\x86\x4d\x2d\x53\x45\x41\x52" \
144 | "\x43\x48\x20\x2a\x20\x48\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a\x48" \
145 | "\x4f\x53\x54\x3a\x20\x32\x33\x39\x2e\x32\x35\x35\x2e\x32\x35\x35" \
146 | "\x2e\x32\x35\x30\x3a\x31\x39\x30\x30\x0d\x0a\x4d\x41\x4e\x3a\x20" \
147 | "\x22\x73\x73\x64\x70\x3a\x64\x69\x73\x63\x6f\x76\x65\x72\x22\x0d" \
148 | "\x0a\x4d\x58\x3a\x20\x31\x0d\x0a\x53\x54\x3a\x20\x75\x72\x6e\x3a" \
149 | "\x64\x69\x61\x6c\x2d\x6d\x75\x6c\x74\x69\x73\x63\x72\x65\x65\x6e" \
150 | "\x2d\x6f\x72\x67\x3a\x73\x65\x72\x76\x69\x63\x65\x3a\x64\x69\x61" \
151 | "\x6c\x3a\x31\x0d\x0a\x0d\x0a"
152 |
153 | assert mr.PacketRelay.modify_udp_packet(udp_packet, 20, newSrcAddr='10.0.0.1',
154 | newSrcPort=1901) == modified_packet
155 |
--------------------------------------------------------------------------------