diff options
Diffstat (limited to 'src/dns/gnunet-dns-monitor.c')
-rw-r--r-- | src/dns/gnunet-dns-monitor.c | 380 |
1 files changed, 199 insertions, 181 deletions
diff --git a/src/dns/gnunet-dns-monitor.c b/src/dns/gnunet-dns-monitor.c index e822b3211..819cb025d 100644 --- a/src/dns/gnunet-dns-monitor.c +++ b/src/dns/gnunet-dns-monitor.c | |||
@@ -11,12 +11,12 @@ | |||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | 11 | WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Affero General Public License for more details. | 13 | Affero General Public License for more details. |
14 | 14 | ||
15 | You should have received a copy of the GNU Affero General Public License | 15 | You should have received a copy of the GNU Affero General Public License |
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | 16 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 | ||
18 | SPDX-License-Identifier: AGPL3.0-or-later | 18 | SPDX-License-Identifier: AGPL3.0-or-later |
19 | */ | 19 | */ |
20 | 20 | ||
21 | /** | 21 | /** |
22 | * @file src/dns/gnunet-dns-monitor.c | 22 | * @file src/dns/gnunet-dns-monitor.c |
@@ -62,22 +62,31 @@ static unsigned int verbosity; | |||
62 | * @return type as string, only valid until the next call to this function | 62 | * @return type as string, only valid until the next call to this function |
63 | */ | 63 | */ |
64 | static const char * | 64 | static const char * |
65 | get_type (uint16_t type) | 65 | get_type(uint16_t type) |
66 | { | 66 | { |
67 | static char buf[6]; | 67 | static char buf[6]; |
68 | |||
68 | switch (type) | 69 | switch (type) |
69 | { | 70 | { |
70 | case GNUNET_DNSPARSER_TYPE_A: return "A"; | 71 | case GNUNET_DNSPARSER_TYPE_A: return "A"; |
71 | case GNUNET_DNSPARSER_TYPE_NS: return "NS"; | 72 | |
72 | case GNUNET_DNSPARSER_TYPE_CNAME: return "CNAME"; | 73 | case GNUNET_DNSPARSER_TYPE_NS: return "NS"; |
73 | case GNUNET_DNSPARSER_TYPE_SOA: return "SOA"; | 74 | |
74 | case GNUNET_DNSPARSER_TYPE_PTR: return "PTR"; | 75 | case GNUNET_DNSPARSER_TYPE_CNAME: return "CNAME"; |
75 | case GNUNET_DNSPARSER_TYPE_MX: return "MX"; | 76 | |
76 | case GNUNET_DNSPARSER_TYPE_TXT: return "TXT"; | 77 | case GNUNET_DNSPARSER_TYPE_SOA: return "SOA"; |
77 | case GNUNET_DNSPARSER_TYPE_AAAA: return "AAAA"; | 78 | |
78 | case GNUNET_DNSPARSER_TYPE_SRV: return "SRV"; | 79 | case GNUNET_DNSPARSER_TYPE_PTR: return "PTR"; |
79 | } | 80 | |
80 | GNUNET_snprintf (buf, sizeof (buf), "%u", (unsigned int) type); | 81 | case GNUNET_DNSPARSER_TYPE_MX: return "MX"; |
82 | |||
83 | case GNUNET_DNSPARSER_TYPE_TXT: return "TXT"; | ||
84 | |||
85 | case GNUNET_DNSPARSER_TYPE_AAAA: return "AAAA"; | ||
86 | |||
87 | case GNUNET_DNSPARSER_TYPE_SRV: return "SRV"; | ||
88 | } | ||
89 | GNUNET_snprintf(buf, sizeof(buf), "%u", (unsigned int)type); | ||
81 | return buf; | 90 | return buf; |
82 | } | 91 | } |
83 | 92 | ||
@@ -89,16 +98,19 @@ get_type (uint16_t type) | |||
89 | * @return class as string, only valid until the next call to this function | 98 | * @return class as string, only valid until the next call to this function |
90 | */ | 99 | */ |
91 | static const char * | 100 | static const char * |
92 | get_class (uint16_t class) | 101 | get_class(uint16_t class) |
93 | { | 102 | { |
94 | static char buf[6]; | 103 | static char buf[6]; |
104 | |||
95 | switch (class) | 105 | switch (class) |
96 | { | 106 | { |
97 | case GNUNET_TUN_DNS_CLASS_INTERNET: return "IN"; | 107 | case GNUNET_TUN_DNS_CLASS_INTERNET: return "IN"; |
98 | case GNUNET_TUN_DNS_CLASS_CHAOS: return "CHAOS"; | 108 | |
99 | case GNUNET_TUN_DNS_CLASS_HESIOD: return "HESIOD"; | 109 | case GNUNET_TUN_DNS_CLASS_CHAOS: return "CHAOS"; |
100 | } | 110 | |
101 | GNUNET_snprintf (buf, sizeof (buf), "%u", (unsigned int) class); | 111 | case GNUNET_TUN_DNS_CLASS_HESIOD: return "HESIOD"; |
112 | } | ||
113 | GNUNET_snprintf(buf, sizeof(buf), "%u", (unsigned int)class); | ||
102 | return buf; | 114 | return buf; |
103 | } | 115 | } |
104 | 116 | ||
@@ -109,13 +121,13 @@ get_class (uint16_t class) | |||
109 | * @param query query to display. | 121 | * @param query query to display. |
110 | */ | 122 | */ |
111 | static void | 123 | static void |
112 | display_query (const struct GNUNET_DNSPARSER_Query *query) | 124 | display_query(const struct GNUNET_DNSPARSER_Query *query) |
113 | { | 125 | { |
114 | fprintf (stdout, | 126 | fprintf(stdout, |
115 | "\t\t%s %s: %s\n", | 127 | "\t\t%s %s: %s\n", |
116 | get_class (query->dns_traffic_class), | 128 | get_class(query->dns_traffic_class), |
117 | get_type (query->type), | 129 | get_type(query->type), |
118 | query->name); | 130 | query->name); |
119 | } | 131 | } |
120 | 132 | ||
121 | 133 | ||
@@ -125,7 +137,7 @@ display_query (const struct GNUNET_DNSPARSER_Query *query) | |||
125 | * @param record record to display. | 137 | * @param record record to display. |
126 | */ | 138 | */ |
127 | static void | 139 | static void |
128 | display_record (const struct GNUNET_DNSPARSER_Record *record) | 140 | display_record(const struct GNUNET_DNSPARSER_Record *record) |
129 | { | 141 | { |
130 | const char *format; | 142 | const char *format; |
131 | char buf[INET6_ADDRSTRLEN]; | 143 | char buf[INET6_ADDRSTRLEN]; |
@@ -133,86 +145,93 @@ display_record (const struct GNUNET_DNSPARSER_Record *record) | |||
133 | 145 | ||
134 | tmp = NULL; | 146 | tmp = NULL; |
135 | switch (record->type) | 147 | switch (record->type) |
136 | { | ||
137 | case GNUNET_DNSPARSER_TYPE_A: | ||
138 | if (record->data.raw.data_len != sizeof (struct in_addr)) | ||
139 | format = "<invalid>"; | ||
140 | else | ||
141 | format = inet_ntop (AF_INET, record->data.raw.data, buf, sizeof (buf)); | ||
142 | break; | ||
143 | case GNUNET_DNSPARSER_TYPE_AAAA: | ||
144 | if (record->data.raw.data_len != sizeof (struct in6_addr)) | ||
145 | format = "<invalid>"; | ||
146 | else | ||
147 | format = inet_ntop (AF_INET6, record->data.raw.data, buf, sizeof (buf)); | ||
148 | break; | ||
149 | case GNUNET_DNSPARSER_TYPE_NS: | ||
150 | case GNUNET_DNSPARSER_TYPE_CNAME: | ||
151 | case GNUNET_DNSPARSER_TYPE_PTR: | ||
152 | format = record->data.hostname; | ||
153 | break; | ||
154 | case GNUNET_DNSPARSER_TYPE_SOA: | ||
155 | if (NULL == record->data.soa) | ||
156 | format = "<invalid>"; | ||
157 | else | ||
158 | { | 148 | { |
159 | GNUNET_asprintf (&tmp, | 149 | case GNUNET_DNSPARSER_TYPE_A: |
160 | "origin: %s, mail: %s, serial = %u, refresh = %u s, retry = %u s, expire = %u s, minimum = %u s", | 150 | if (record->data.raw.data_len != sizeof(struct in_addr)) |
161 | record->data.soa->mname, | 151 | format = "<invalid>"; |
162 | record->data.soa->rname, | 152 | else |
163 | (unsigned int) record->data.soa->serial, | 153 | format = inet_ntop(AF_INET, record->data.raw.data, buf, sizeof(buf)); |
164 | (unsigned int) record->data.soa->refresh, | 154 | break; |
165 | (unsigned int) record->data.soa->retry, | 155 | |
166 | (unsigned int) record->data.soa->expire, | 156 | case GNUNET_DNSPARSER_TYPE_AAAA: |
167 | (unsigned int) record->data.soa->minimum_ttl); | 157 | if (record->data.raw.data_len != sizeof(struct in6_addr)) |
168 | format = tmp; | 158 | format = "<invalid>"; |
169 | } | 159 | else |
170 | break; | 160 | format = inet_ntop(AF_INET6, record->data.raw.data, buf, sizeof(buf)); |
171 | case GNUNET_DNSPARSER_TYPE_MX: | 161 | break; |
172 | if (record->data.mx == NULL) | 162 | |
173 | format = "<invalid>"; | 163 | case GNUNET_DNSPARSER_TYPE_NS: |
174 | else | 164 | case GNUNET_DNSPARSER_TYPE_CNAME: |
175 | { | 165 | case GNUNET_DNSPARSER_TYPE_PTR: |
176 | GNUNET_asprintf (&tmp, | 166 | format = record->data.hostname; |
177 | "%u: %s", | 167 | break; |
178 | record->data.mx->preference, | 168 | |
179 | record->data.mx->mxhost); | 169 | case GNUNET_DNSPARSER_TYPE_SOA: |
180 | format = tmp; | 170 | if (NULL == record->data.soa) |
181 | } | 171 | format = "<invalid>"; |
182 | break; | 172 | else |
183 | case GNUNET_DNSPARSER_TYPE_SRV: | 173 | { |
184 | if (NULL == record->data.srv) | 174 | GNUNET_asprintf(&tmp, |
185 | format = "<invalid>"; | 175 | "origin: %s, mail: %s, serial = %u, refresh = %u s, retry = %u s, expire = %u s, minimum = %u s", |
186 | else | 176 | record->data.soa->mname, |
187 | { | 177 | record->data.soa->rname, |
188 | GNUNET_asprintf (&tmp, | 178 | (unsigned int)record->data.soa->serial, |
189 | "priority %u, weight = %s, port = %u, target = %s", | 179 | (unsigned int)record->data.soa->refresh, |
190 | (unsigned int) record->data.srv->priority, | 180 | (unsigned int)record->data.soa->retry, |
191 | (unsigned int) record->data.srv->weight, | 181 | (unsigned int)record->data.soa->expire, |
192 | (unsigned int) record->data.srv->port, | 182 | (unsigned int)record->data.soa->minimum_ttl); |
193 | record->data.srv->target); | 183 | format = tmp; |
184 | } | ||
185 | break; | ||
186 | |||
187 | case GNUNET_DNSPARSER_TYPE_MX: | ||
188 | if (record->data.mx == NULL) | ||
189 | format = "<invalid>"; | ||
190 | else | ||
191 | { | ||
192 | GNUNET_asprintf(&tmp, | ||
193 | "%u: %s", | ||
194 | record->data.mx->preference, | ||
195 | record->data.mx->mxhost); | ||
196 | format = tmp; | ||
197 | } | ||
198 | break; | ||
199 | |||
200 | case GNUNET_DNSPARSER_TYPE_SRV: | ||
201 | if (NULL == record->data.srv) | ||
202 | format = "<invalid>"; | ||
203 | else | ||
204 | { | ||
205 | GNUNET_asprintf(&tmp, | ||
206 | "priority %u, weight = %s, port = %u, target = %s", | ||
207 | (unsigned int)record->data.srv->priority, | ||
208 | (unsigned int)record->data.srv->weight, | ||
209 | (unsigned int)record->data.srv->port, | ||
210 | record->data.srv->target); | ||
211 | format = tmp; | ||
212 | } | ||
213 | break; | ||
214 | |||
215 | case GNUNET_DNSPARSER_TYPE_TXT: | ||
216 | GNUNET_asprintf(&tmp, | ||
217 | "%.*s", | ||
218 | (unsigned int)record->data.raw.data_len, | ||
219 | record->data.raw.data); | ||
194 | format = tmp; | 220 | format = tmp; |
221 | break; | ||
222 | |||
223 | default: | ||
224 | format = "<payload>"; | ||
225 | break; | ||
195 | } | 226 | } |
196 | break; | 227 | fprintf(stdout, |
197 | case GNUNET_DNSPARSER_TYPE_TXT: | 228 | "\t\t%s %s: %s = %s (%u s)\n", |
198 | GNUNET_asprintf (&tmp, | 229 | get_class(record->dns_traffic_class), |
199 | "%.*s", | 230 | get_type(record->type), |
200 | (unsigned int) record->data.raw.data_len, | 231 | record->name, |
201 | record->data.raw.data); | 232 | format, |
202 | format = tmp; | 233 | (unsigned int)(GNUNET_TIME_absolute_get_remaining(record->expiration_time).rel_value_us / 1000LL / 1000LL)); |
203 | break; | 234 | GNUNET_free_non_null(tmp); |
204 | default: | ||
205 | format = "<payload>"; | ||
206 | break; | ||
207 | } | ||
208 | fprintf (stdout, | ||
209 | "\t\t%s %s: %s = %s (%u s)\n", | ||
210 | get_class (record->dns_traffic_class), | ||
211 | get_type (record->type), | ||
212 | record->name, | ||
213 | format, | ||
214 | (unsigned int) (GNUNET_TIME_absolute_get_remaining (record->expiration_time).rel_value_us / 1000LL / 1000LL)); | ||
215 | GNUNET_free_non_null (tmp); | ||
216 | } | 235 | } |
217 | 236 | ||
218 | 237 | ||
@@ -240,62 +259,62 @@ display_record (const struct GNUNET_DNSPARSER_Record *record) | |||
240 | * @param request udp payload of the DNS request | 259 | * @param request udp payload of the DNS request |
241 | */ | 260 | */ |
242 | static void | 261 | static void |
243 | display_request (void *cls, | 262 | display_request(void *cls, |
244 | struct GNUNET_DNS_RequestHandle *rh, | 263 | struct GNUNET_DNS_RequestHandle *rh, |
245 | size_t request_length, | 264 | size_t request_length, |
246 | const char *request) | 265 | const char *request) |
247 | { | 266 | { |
248 | static const char *return_codes[] = | 267 | static const char *return_codes[] = |
249 | { | 268 | { |
250 | "No error", "Format error", "Server failure", "Name error", | 269 | "No error", "Format error", "Server failure", "Name error", |
251 | "Not implemented", "Refused", "YXDomain", "YXRRset", | 270 | "Not implemented", "Refused", "YXDomain", "YXRRset", |
252 | "NXRRset", "NOT AUTH", "NOT ZONE", "<invalid>", | 271 | "NXRRset", "NOT AUTH", "NOT ZONE", "<invalid>", |
253 | "<invalid>", "<invalid>", "<invalid>", "<invalid>" | 272 | "<invalid>", "<invalid>", "<invalid>", "<invalid>" |
254 | }; | 273 | }; |
255 | static const char *op_codes[] = | 274 | static const char *op_codes[] = |
256 | { | 275 | { |
257 | "Query", "Inverse query", "Status", "<invalid>", | 276 | "Query", "Inverse query", "Status", "<invalid>", |
258 | "<invalid>", "<invalid>", "<invalid>", "<invalid>", | 277 | "<invalid>", "<invalid>", "<invalid>", "<invalid>", |
259 | "<invalid>", "<invalid>", "<invalid>", "<invalid>", | 278 | "<invalid>", "<invalid>", "<invalid>", "<invalid>", |
260 | "<invalid>", "<invalid>", "<invalid>", "<invalid>" | 279 | "<invalid>", "<invalid>", "<invalid>", "<invalid>" |
261 | }; | 280 | }; |
262 | struct GNUNET_DNSPARSER_Packet *p; | 281 | struct GNUNET_DNSPARSER_Packet *p; |
263 | unsigned int i; | 282 | unsigned int i; |
264 | 283 | ||
265 | p = GNUNET_DNSPARSER_parse (request, request_length); | 284 | p = GNUNET_DNSPARSER_parse(request, request_length); |
266 | if (NULL == p) | 285 | if (NULL == p) |
267 | { | 286 | { |
268 | fprintf (stderr, "Received malformed DNS packet!\n"); | 287 | fprintf(stderr, "Received malformed DNS packet!\n"); |
269 | // FIXME: drop instead? | 288 | // FIXME: drop instead? |
270 | GNUNET_DNS_request_forward (rh); | 289 | GNUNET_DNS_request_forward(rh); |
271 | return; | 290 | return; |
272 | } | 291 | } |
273 | fprintf (stdout, | 292 | fprintf(stdout, |
274 | "%s with ID: %5u Flags: %s%s%s%s%s%s, Return Code: %s, Opcode: %s\n", | 293 | "%s with ID: %5u Flags: %s%s%s%s%s%s, Return Code: %s, Opcode: %s\n", |
275 | p->flags.query_or_response ? "Response" : "Query", | 294 | p->flags.query_or_response ? "Response" : "Query", |
276 | p->id, | 295 | p->id, |
277 | p->flags.recursion_desired ? "RD " : "", | 296 | p->flags.recursion_desired ? "RD " : "", |
278 | p->flags.message_truncated ? "MT " : "", | 297 | p->flags.message_truncated ? "MT " : "", |
279 | p->flags.authoritative_answer ? "AA " : "", | 298 | p->flags.authoritative_answer ? "AA " : "", |
280 | p->flags.checking_disabled ? "CD " : "", | 299 | p->flags.checking_disabled ? "CD " : "", |
281 | p->flags.authenticated_data ? "AD " : "", | 300 | p->flags.authenticated_data ? "AD " : "", |
282 | p->flags.recursion_available ? "RA " : "", | 301 | p->flags.recursion_available ? "RA " : "", |
283 | return_codes[p->flags.return_code & 15], | 302 | return_codes[p->flags.return_code & 15], |
284 | op_codes[p->flags.opcode & 15]); | 303 | op_codes[p->flags.opcode & 15]); |
285 | if (p->num_queries > 0) | 304 | if (p->num_queries > 0) |
286 | fprintf (stdout, | 305 | fprintf(stdout, |
287 | "\tQueries:\n"); | 306 | "\tQueries:\n"); |
288 | for (i=0;i<p->num_queries;i++) | 307 | for (i = 0; i < p->num_queries; i++) |
289 | display_query (&p->queries[i]); | 308 | display_query(&p->queries[i]); |
290 | 309 | ||
291 | if (p->num_answers > 0) | 310 | if (p->num_answers > 0) |
292 | fprintf (stdout, | 311 | fprintf(stdout, |
293 | "\tAnswers:\n"); | 312 | "\tAnswers:\n"); |
294 | for (i=0;i<p->num_answers;i++) | 313 | for (i = 0; i < p->num_answers; i++) |
295 | display_record (&p->answers[i]); | 314 | display_record(&p->answers[i]); |
296 | fprintf (stdout, "\n"); | 315 | fprintf(stdout, "\n"); |
297 | GNUNET_DNSPARSER_free_packet (p); | 316 | GNUNET_DNSPARSER_free_packet(p); |
298 | GNUNET_DNS_request_forward (rh); | 317 | GNUNET_DNS_request_forward(rh); |
299 | } | 318 | } |
300 | 319 | ||
301 | 320 | ||
@@ -303,13 +322,13 @@ display_request (void *cls, | |||
303 | * Shutdown. | 322 | * Shutdown. |
304 | */ | 323 | */ |
305 | static void | 324 | static void |
306 | do_disconnect (void *cls) | 325 | do_disconnect(void *cls) |
307 | { | 326 | { |
308 | if (NULL != handle) | 327 | if (NULL != handle) |
309 | { | 328 | { |
310 | GNUNET_DNS_disconnect (handle); | 329 | GNUNET_DNS_disconnect(handle); |
311 | handle = NULL; | 330 | handle = NULL; |
312 | } | 331 | } |
313 | } | 332 | } |
314 | 333 | ||
315 | 334 | ||
@@ -322,8 +341,8 @@ do_disconnect (void *cls) | |||
322 | * @param cfg configuration | 341 | * @param cfg configuration |
323 | */ | 342 | */ |
324 | static void | 343 | static void |
325 | run (void *cls, char *const *args, const char *cfgfile, | 344 | run(void *cls, char *const *args, const char *cfgfile, |
326 | const struct GNUNET_CONFIGURATION_Handle *cfg) | 345 | const struct GNUNET_CONFIGURATION_Handle *cfg) |
327 | { | 346 | { |
328 | enum GNUNET_DNS_Flags flags; | 347 | enum GNUNET_DNS_Flags flags; |
329 | 348 | ||
@@ -335,41 +354,40 @@ run (void *cls, char *const *args, const char *cfgfile, | |||
335 | if (outbound_only) | 354 | if (outbound_only) |
336 | flags |= GNUNET_DNS_FLAG_RESPONSE_MONITOR; | 355 | flags |= GNUNET_DNS_FLAG_RESPONSE_MONITOR; |
337 | handle = | 356 | handle = |
338 | GNUNET_DNS_connect (cfg, | 357 | GNUNET_DNS_connect(cfg, |
339 | flags, | 358 | flags, |
340 | &display_request, | 359 | &display_request, |
341 | NULL); | 360 | NULL); |
342 | GNUNET_SCHEDULER_add_shutdown (&do_disconnect, NULL); | 361 | GNUNET_SCHEDULER_add_shutdown(&do_disconnect, NULL); |
343 | } | 362 | } |
344 | 363 | ||
345 | 364 | ||
346 | int | 365 | int |
347 | main (int argc, char *const *argv) | 366 | main(int argc, char *const *argv) |
348 | { | 367 | { |
349 | struct GNUNET_GETOPT_CommandLineOption options[] = { | 368 | struct GNUNET_GETOPT_CommandLineOption options[] = { |
369 | GNUNET_GETOPT_option_flag('i', | ||
370 | "inbound-only", | ||
371 | gettext_noop("only monitor DNS queries"), | ||
372 | &inbound_only), | ||
350 | 373 | ||
351 | GNUNET_GETOPT_option_flag ('i', | 374 | GNUNET_GETOPT_option_flag('o', |
352 | "inbound-only", | 375 | "outbound-only", |
353 | gettext_noop ("only monitor DNS queries"), | 376 | gettext_noop("only monitor DNS queries"), |
354 | &inbound_only), | 377 | &outbound_only), |
355 | |||
356 | GNUNET_GETOPT_option_flag ('o', | ||
357 | "outbound-only", | ||
358 | gettext_noop ("only monitor DNS queries"), | ||
359 | &outbound_only), | ||
360 | 378 | ||
361 | GNUNET_GETOPT_option_verbose (&verbosity), | 379 | GNUNET_GETOPT_option_verbose(&verbosity), |
362 | GNUNET_GETOPT_OPTION_END | 380 | GNUNET_GETOPT_OPTION_END |
363 | }; | 381 | }; |
364 | 382 | ||
365 | if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) | 383 | if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args(argc, argv, &argc, &argv)) |
366 | return 2; | 384 | return 2; |
367 | ret = (GNUNET_OK == | 385 | ret = (GNUNET_OK == |
368 | GNUNET_PROGRAM_run (argc, argv, "gnunet-dns-monitor", | 386 | GNUNET_PROGRAM_run(argc, argv, "gnunet-dns-monitor", |
369 | gettext_noop | 387 | gettext_noop |
370 | ("Monitor DNS queries."), options, | 388 | ("Monitor DNS queries."), options, |
371 | &run, NULL)) ? ret : 1; | 389 | &run, NULL)) ? ret : 1; |
372 | GNUNET_free ((void*) argv); | 390 | GNUNET_free((void*)argv); |
373 | return ret; | 391 | return ret; |
374 | } | 392 | } |
375 | 393 | ||