Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 676 lines (600 sloc) 19.39 kb
68396ea Robert Mustacchi Initial commit of d32e8d0b8d9e0ef7cf7ab2e74548982972789dfc from qemu-kvm
rmustacc authored
1 /*
2 * inet and unix socket functions for qemu
3 *
4 * (c) 2008 Gerd Hoffmann <kraxel@redhat.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; under version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 */
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <errno.h>
20 #include <unistd.h>
21
22 #include "qemu_socket.h"
23 #include "qemu-common.h" /* for qemu_isdigit */
24
25 #ifndef AI_ADDRCONFIG
26 # define AI_ADDRCONFIG 0
27 #endif
28
29 static int sockets_debug = 0;
30 static const int on=1, off=0;
31
32 /* used temporarely until all users are converted to QemuOpts */
33 static QemuOptsList dummy_opts = {
34 .name = "dummy",
35 .head = QTAILQ_HEAD_INITIALIZER(dummy_opts.head),
36 .desc = {
37 {
38 .name = "path",
39 .type = QEMU_OPT_STRING,
40 },{
41 .name = "host",
42 .type = QEMU_OPT_STRING,
43 },{
44 .name = "port",
45 .type = QEMU_OPT_STRING,
46 },{
47 .name = "to",
48 .type = QEMU_OPT_NUMBER,
49 },{
50 .name = "ipv4",
51 .type = QEMU_OPT_BOOL,
52 },{
53 .name = "ipv6",
54 .type = QEMU_OPT_BOOL,
55 },
56 { /* end if list */ }
57 },
58 };
59
60 static int inet_getport(struct addrinfo *e)
61 {
62 struct sockaddr_in *i4;
63 struct sockaddr_in6 *i6;
64
65 switch (e->ai_family) {
66 case PF_INET6:
67 i6 = (void*)e->ai_addr;
68 return ntohs(i6->sin6_port);
69 case PF_INET:
70 i4 = (void*)e->ai_addr;
71 return ntohs(i4->sin_port);
72 default:
73 return 0;
74 }
75 }
76
77 static void inet_setport(struct addrinfo *e, int port)
78 {
79 struct sockaddr_in *i4;
80 struct sockaddr_in6 *i6;
81
82 switch (e->ai_family) {
83 case PF_INET6:
84 i6 = (void*)e->ai_addr;
85 i6->sin6_port = htons(port);
86 break;
87 case PF_INET:
88 i4 = (void*)e->ai_addr;
89 i4->sin_port = htons(port);
90 break;
91 }
92 }
93
94 const char *inet_strfamily(int family)
95 {
96 switch (family) {
97 case PF_INET6: return "ipv6";
98 case PF_INET: return "ipv4";
99 case PF_UNIX: return "unix";
100 }
101 return "unknown";
102 }
103
104 static void inet_print_addrinfo(const char *tag, struct addrinfo *res)
105 {
106 struct addrinfo *e;
107 char uaddr[INET6_ADDRSTRLEN+1];
108 char uport[33];
109
110 for (e = res; e != NULL; e = e->ai_next) {
111 getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
112 uaddr,INET6_ADDRSTRLEN,uport,32,
113 NI_NUMERICHOST | NI_NUMERICSERV);
114 fprintf(stderr,"%s: getaddrinfo: family %s, host %s, port %s\n",
115 tag, inet_strfamily(e->ai_family), uaddr, uport);
116 }
117 }
118
119 int inet_listen_opts(QemuOpts *opts, int port_offset)
120 {
121 struct addrinfo ai,*res,*e;
122 const char *addr;
123 char port[33];
124 char uaddr[INET6_ADDRSTRLEN+1];
125 char uport[33];
126 int slisten,rc,to,try_next;
127
128 memset(&ai,0, sizeof(ai));
129 ai.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
130 ai.ai_family = PF_UNSPEC;
131 ai.ai_socktype = SOCK_STREAM;
132
133 if ((qemu_opt_get(opts, "host") == NULL) ||
134 (qemu_opt_get(opts, "port") == NULL)) {
135 fprintf(stderr, "%s: host and/or port not specified\n", __FUNCTION__);
136 return -1;
137 }
138 pstrcpy(port, sizeof(port), qemu_opt_get(opts, "port"));
139 addr = qemu_opt_get(opts, "host");
140
141 to = qemu_opt_get_number(opts, "to", 0);
142 if (qemu_opt_get_bool(opts, "ipv4", 0))
143 ai.ai_family = PF_INET;
144 if (qemu_opt_get_bool(opts, "ipv6", 0))
145 ai.ai_family = PF_INET6;
146
147 /* lookup */
148 if (port_offset)
149 snprintf(port, sizeof(port), "%d", atoi(port) + port_offset);
150 rc = getaddrinfo(strlen(addr) ? addr : NULL, port, &ai, &res);
151 if (rc != 0) {
152 fprintf(stderr,"getaddrinfo(%s,%s): %s\n", addr, port,
153 gai_strerror(rc));
154 return -1;
155 }
156 if (sockets_debug)
157 inet_print_addrinfo(__FUNCTION__, res);
158
159 /* create socket + bind */
160 for (e = res; e != NULL; e = e->ai_next) {
161 getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
162 uaddr,INET6_ADDRSTRLEN,uport,32,
163 NI_NUMERICHOST | NI_NUMERICSERV);
164 slisten = qemu_socket(e->ai_family, e->ai_socktype, e->ai_protocol);
165 if (slisten < 0) {
166 fprintf(stderr,"%s: socket(%s): %s\n", __FUNCTION__,
167 inet_strfamily(e->ai_family), strerror(errno));
168 continue;
169 }
170
171 setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on));
172 #ifdef IPV6_V6ONLY
173 if (e->ai_family == PF_INET6) {
174 /* listen on both ipv4 and ipv6 */
175 setsockopt(slisten,IPPROTO_IPV6,IPV6_V6ONLY,(void*)&off,
176 sizeof(off));
177 }
178 #endif
179
180 for (;;) {
181 if (bind(slisten, e->ai_addr, e->ai_addrlen) == 0) {
182 if (sockets_debug)
183 fprintf(stderr,"%s: bind(%s,%s,%d): OK\n", __FUNCTION__,
184 inet_strfamily(e->ai_family), uaddr, inet_getport(e));
185 goto listen;
186 }
187 try_next = to && (inet_getport(e) <= to + port_offset);
188 if (!try_next || sockets_debug)
189 fprintf(stderr,"%s: bind(%s,%s,%d): %s\n", __FUNCTION__,
190 inet_strfamily(e->ai_family), uaddr, inet_getport(e),
191 strerror(errno));
192 if (try_next) {
193 inet_setport(e, inet_getport(e) + 1);
194 continue;
195 }
196 break;
197 }
198 closesocket(slisten);
199 }
200 fprintf(stderr, "%s: FAILED\n", __FUNCTION__);
201 freeaddrinfo(res);
202 return -1;
203
204 listen:
205 if (listen(slisten,1) != 0) {
206 perror("listen");
207 closesocket(slisten);
208 freeaddrinfo(res);
209 return -1;
210 }
211 snprintf(uport, sizeof(uport), "%d", inet_getport(e) - port_offset);
212 qemu_opt_set(opts, "host", uaddr);
213 qemu_opt_set(opts, "port", uport);
214 qemu_opt_set(opts, "ipv6", (e->ai_family == PF_INET6) ? "on" : "off");
215 qemu_opt_set(opts, "ipv4", (e->ai_family != PF_INET6) ? "on" : "off");
216 freeaddrinfo(res);
217 return slisten;
218 }
219
220 int inet_connect_opts(QemuOpts *opts)
221 {
222 struct addrinfo ai,*res,*e;
223 const char *addr;
224 const char *port;
225 char uaddr[INET6_ADDRSTRLEN+1];
226 char uport[33];
227 int sock,rc;
228
229 memset(&ai,0, sizeof(ai));
230 ai.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
231 ai.ai_family = PF_UNSPEC;
232 ai.ai_socktype = SOCK_STREAM;
233
234 addr = qemu_opt_get(opts, "host");
235 port = qemu_opt_get(opts, "port");
236 if (addr == NULL || port == NULL) {
237 fprintf(stderr, "inet_connect: host and/or port not specified\n");
238 return -1;
239 }
240
241 if (qemu_opt_get_bool(opts, "ipv4", 0))
242 ai.ai_family = PF_INET;
243 if (qemu_opt_get_bool(opts, "ipv6", 0))
244 ai.ai_family = PF_INET6;
245
246 /* lookup */
247 if (0 != (rc = getaddrinfo(addr, port, &ai, &res))) {
248 fprintf(stderr,"getaddrinfo(%s,%s): %s\n", addr, port,
249 gai_strerror(rc));
250 return -1;
251 }
252 if (sockets_debug)
253 inet_print_addrinfo(__FUNCTION__, res);
254
255 for (e = res; e != NULL; e = e->ai_next) {
256 if (getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
257 uaddr,INET6_ADDRSTRLEN,uport,32,
258 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
259 fprintf(stderr,"%s: getnameinfo: oops\n", __FUNCTION__);
260 continue;
261 }
262 sock = qemu_socket(e->ai_family, e->ai_socktype, e->ai_protocol);
263 if (sock < 0) {
264 fprintf(stderr,"%s: socket(%s): %s\n", __FUNCTION__,
265 inet_strfamily(e->ai_family), strerror(errno));
266 continue;
267 }
268 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on));
269
270 /* connect to peer */
271 if (connect(sock,e->ai_addr,e->ai_addrlen) < 0) {
272 if (sockets_debug || NULL == e->ai_next)
273 fprintf(stderr, "%s: connect(%s,%s,%s,%s): %s\n", __FUNCTION__,
274 inet_strfamily(e->ai_family),
275 e->ai_canonname, uaddr, uport, strerror(errno));
276 closesocket(sock);
277 continue;
278 }
279 if (sockets_debug)
280 fprintf(stderr, "%s: connect(%s,%s,%s,%s): OK\n", __FUNCTION__,
281 inet_strfamily(e->ai_family),
282 e->ai_canonname, uaddr, uport);
283 freeaddrinfo(res);
284 return sock;
285 }
286 freeaddrinfo(res);
287 return -1;
288 }
289
290 int inet_dgram_opts(QemuOpts *opts)
291 {
292 struct addrinfo ai, *peer = NULL, *local = NULL;
293 const char *addr;
294 const char *port;
295 char uaddr[INET6_ADDRSTRLEN+1];
296 char uport[33];
297 int sock = -1, rc;
298
299 /* lookup peer addr */
300 memset(&ai,0, sizeof(ai));
301 ai.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
302 ai.ai_family = PF_UNSPEC;
303 ai.ai_socktype = SOCK_DGRAM;
304
305 addr = qemu_opt_get(opts, "host");
306 port = qemu_opt_get(opts, "port");
307 if (addr == NULL || strlen(addr) == 0) {
308 addr = "localhost";
309 }
310 if (port == NULL || strlen(port) == 0) {
311 fprintf(stderr, "inet_dgram: port not specified\n");
312 return -1;
313 }
314
315 if (qemu_opt_get_bool(opts, "ipv4", 0))
316 ai.ai_family = PF_INET;
317 if (qemu_opt_get_bool(opts, "ipv6", 0))
318 ai.ai_family = PF_INET6;
319
320 if (0 != (rc = getaddrinfo(addr, port, &ai, &peer))) {
321 fprintf(stderr,"getaddrinfo(%s,%s): %s\n", addr, port,
322 gai_strerror(rc));
323 return -1;
324 }
325 if (sockets_debug) {
326 fprintf(stderr, "%s: peer (%s:%s)\n", __FUNCTION__, addr, port);
327 inet_print_addrinfo(__FUNCTION__, peer);
328 }
329
330 /* lookup local addr */
331 memset(&ai,0, sizeof(ai));
332 ai.ai_flags = AI_PASSIVE;
333 ai.ai_family = peer->ai_family;
334 ai.ai_socktype = SOCK_DGRAM;
335
336 addr = qemu_opt_get(opts, "localaddr");
337 port = qemu_opt_get(opts, "localport");
338 if (addr == NULL || strlen(addr) == 0) {
339 addr = NULL;
340 }
341 if (!port || strlen(port) == 0)
342 port = "0";
343
344 if (0 != (rc = getaddrinfo(addr, port, &ai, &local))) {
345 fprintf(stderr,"getaddrinfo(%s,%s): %s\n", addr, port,
346 gai_strerror(rc));
347 return -1;
348 }
349 if (sockets_debug) {
350 fprintf(stderr, "%s: local (%s:%s)\n", __FUNCTION__, addr, port);
351 inet_print_addrinfo(__FUNCTION__, local);
352 }
353
354 /* create socket */
355 sock = qemu_socket(peer->ai_family, peer->ai_socktype, peer->ai_protocol);
356 if (sock < 0) {
357 fprintf(stderr,"%s: socket(%s): %s\n", __FUNCTION__,
358 inet_strfamily(peer->ai_family), strerror(errno));
359 goto err;
360 }
361 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on));
362
363 /* bind socket */
364 if (getnameinfo((struct sockaddr*)local->ai_addr,local->ai_addrlen,
365 uaddr,INET6_ADDRSTRLEN,uport,32,
366 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
367 fprintf(stderr, "%s: getnameinfo: oops\n", __FUNCTION__);
368 goto err;
369 }
370 if (bind(sock, local->ai_addr, local->ai_addrlen) < 0) {
371 fprintf(stderr,"%s: bind(%s,%s,%d): OK\n", __FUNCTION__,
372 inet_strfamily(local->ai_family), uaddr, inet_getport(local));
373 goto err;
374 }
375
376 /* connect to peer */
377 if (getnameinfo((struct sockaddr*)peer->ai_addr, peer->ai_addrlen,
378 uaddr, INET6_ADDRSTRLEN, uport, 32,
379 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
380 fprintf(stderr, "%s: getnameinfo: oops\n", __FUNCTION__);
381 goto err;
382 }
383 if (connect(sock,peer->ai_addr,peer->ai_addrlen) < 0) {
384 fprintf(stderr, "%s: connect(%s,%s,%s,%s): %s\n", __FUNCTION__,
385 inet_strfamily(peer->ai_family),
386 peer->ai_canonname, uaddr, uport, strerror(errno));
387 goto err;
388 }
389
390 freeaddrinfo(local);
391 freeaddrinfo(peer);
392 return sock;
393
394 err:
395 if (-1 != sock)
396 closesocket(sock);
397 if (local)
398 freeaddrinfo(local);
399 if (peer)
400 freeaddrinfo(peer);
401 return -1;
402 }
403
404 /* compatibility wrapper */
405 static int inet_parse(QemuOpts *opts, const char *str)
406 {
407 const char *optstr, *h;
408 char addr[64];
409 char port[33];
410 int pos;
411
412 /* parse address */
413 if (str[0] == ':') {
414 /* no host given */
415 addr[0] = '\0';
416 if (1 != sscanf(str,":%32[^,]%n",port,&pos)) {
417 fprintf(stderr, "%s: portonly parse error (%s)\n",
418 __FUNCTION__, str);
419 return -1;
420 }
421 } else if (str[0] == '[') {
422 /* IPv6 addr */
423 if (2 != sscanf(str,"[%64[^]]]:%32[^,]%n",addr,port,&pos)) {
424 fprintf(stderr, "%s: ipv6 parse error (%s)\n",
425 __FUNCTION__, str);
426 return -1;
427 }
428 qemu_opt_set(opts, "ipv6", "on");
429 } else if (qemu_isdigit(str[0])) {
430 /* IPv4 addr */
431 if (2 != sscanf(str,"%64[0-9.]:%32[^,]%n",addr,port,&pos)) {
432 fprintf(stderr, "%s: ipv4 parse error (%s)\n",
433 __FUNCTION__, str);
434 return -1;
435 }
436 qemu_opt_set(opts, "ipv4", "on");
437 } else {
438 /* hostname */
439 if (2 != sscanf(str,"%64[^:]:%32[^,]%n",addr,port,&pos)) {
440 fprintf(stderr, "%s: hostname parse error (%s)\n",
441 __FUNCTION__, str);
442 return -1;
443 }
444 }
445 qemu_opt_set(opts, "host", addr);
446 qemu_opt_set(opts, "port", port);
447
448 /* parse options */
449 optstr = str + pos;
450 h = strstr(optstr, ",to=");
451 if (h)
452 qemu_opt_set(opts, "to", h+4);
453 if (strstr(optstr, ",ipv4"))
454 qemu_opt_set(opts, "ipv4", "on");
455 if (strstr(optstr, ",ipv6"))
456 qemu_opt_set(opts, "ipv6", "on");
457 return 0;
458 }
459
460 int inet_listen(const char *str, char *ostr, int olen,
461 int socktype, int port_offset)
462 {
463 QemuOpts *opts;
464 char *optstr;
465 int sock = -1;
466
467 opts = qemu_opts_create(&dummy_opts, NULL, 0);
468 if (inet_parse(opts, str) == 0) {
469 sock = inet_listen_opts(opts, port_offset);
470 if (sock != -1 && ostr) {
471 optstr = strchr(str, ',');
472 if (qemu_opt_get_bool(opts, "ipv6", 0)) {
473 snprintf(ostr, olen, "[%s]:%s%s",
474 qemu_opt_get(opts, "host"),
475 qemu_opt_get(opts, "port"),
476 optstr ? optstr : "");
477 } else {
478 snprintf(ostr, olen, "%s:%s%s",
479 qemu_opt_get(opts, "host"),
480 qemu_opt_get(opts, "port"),
481 optstr ? optstr : "");
482 }
483 }
484 }
485 qemu_opts_del(opts);
486 return sock;
487 }
488
489 int inet_connect(const char *str, int socktype)
490 {
491 QemuOpts *opts;
492 int sock = -1;
493
494 opts = qemu_opts_create(&dummy_opts, NULL, 0);
495 if (inet_parse(opts, str) == 0)
496 sock = inet_connect_opts(opts);
497 qemu_opts_del(opts);
498 return sock;
499 }
500
501 #ifndef _WIN32
502
503 int unix_listen_opts(QemuOpts *opts)
504 {
505 struct sockaddr_un un;
506 const char *path = qemu_opt_get(opts, "path");
507 int sock, fd;
508
509 sock = qemu_socket(PF_UNIX, SOCK_STREAM, 0);
510 if (sock < 0) {
511 perror("socket(unix)");
512 return -1;
513 }
514
515 memset(&un, 0, sizeof(un));
516 un.sun_family = AF_UNIX;
517 if (path && strlen(path)) {
518 snprintf(un.sun_path, sizeof(un.sun_path), "%s", path);
519 } else {
520 char *tmpdir = getenv("TMPDIR");
521 snprintf(un.sun_path, sizeof(un.sun_path), "%s/qemu-socket-XXXXXX",
522 tmpdir ? tmpdir : "/tmp");
523 /*
524 * This dummy fd usage silences the mktemp() unsecure warning.
525 * Using mkstemp() doesn't make things more secure here
526 * though. bind() complains about existing files, so we have
527 * to unlink first and thus re-open the race window. The
528 * worst case possible is bind() failing, i.e. a DoS attack.
529 */
530 fd = mkstemp(un.sun_path); close(fd);
531 qemu_opt_set(opts, "path", un.sun_path);
532 }
533
534 unlink(un.sun_path);
535 if (bind(sock, (struct sockaddr*) &un, sizeof(un)) < 0) {
536 fprintf(stderr, "bind(unix:%s): %s\n", un.sun_path, strerror(errno));
537 goto err;
538 }
539 if (listen(sock, 1) < 0) {
540 fprintf(stderr, "listen(unix:%s): %s\n", un.sun_path, strerror(errno));
541 goto err;
542 }
543
544 if (sockets_debug)
545 fprintf(stderr, "bind(unix:%s): OK\n", un.sun_path);
546 return sock;
547
548 err:
549 closesocket(sock);
550 return -1;
551 }
552
553 int unix_connect_opts(QemuOpts *opts)
554 {
555 struct sockaddr_un un;
556 const char *path = qemu_opt_get(opts, "path");
557 int sock;
558
559 if (NULL == path) {
560 fprintf(stderr, "unix connect: no path specified\n");
561 return -1;
562 }
563
564 sock = qemu_socket(PF_UNIX, SOCK_STREAM, 0);
565 if (sock < 0) {
566 perror("socket(unix)");
567 return -1;
568 }
569
570 memset(&un, 0, sizeof(un));
571 un.sun_family = AF_UNIX;
572 snprintf(un.sun_path, sizeof(un.sun_path), "%s", path);
573 if (connect(sock, (struct sockaddr*) &un, sizeof(un)) < 0) {
574 fprintf(stderr, "connect(unix:%s): %s\n", path, strerror(errno));
575 return -1;
576 }
577
578 if (sockets_debug)
579 fprintf(stderr, "connect(unix:%s): OK\n", path);
580 return sock;
581 }
582
583 /* compatibility wrapper */
584 int unix_listen(const char *str, char *ostr, int olen)
585 {
586 QemuOpts *opts;
587 char *path, *optstr;
588 int sock, len;
589
590 opts = qemu_opts_create(&dummy_opts, NULL, 0);
591
592 optstr = strchr(str, ',');
593 if (optstr) {
594 len = optstr - str;
595 if (len) {
596 path = qemu_malloc(len+1);
597 snprintf(path, len+1, "%.*s", len, str);
598 qemu_opt_set(opts, "path", path);
599 qemu_free(path);
600 }
601 } else {
602 qemu_opt_set(opts, "path", str);
603 }
604
605 sock = unix_listen_opts(opts);
606
607 if (sock != -1 && ostr)
608 snprintf(ostr, olen, "%s%s", qemu_opt_get(opts, "path"), optstr ? optstr : "");
609 qemu_opts_del(opts);
610 return sock;
611 }
612
613 int unix_connect(const char *path)
614 {
615 QemuOpts *opts;
616 int sock;
617
618 opts = qemu_opts_create(&dummy_opts, NULL, 0);
619 qemu_opt_set(opts, "path", path);
620 sock = unix_connect_opts(opts);
621 qemu_opts_del(opts);
622 return sock;
623 }
624
625 #else
626
627 int unix_listen_opts(QemuOpts *opts)
628 {
629 fprintf(stderr, "unix sockets are not available on windows\n");
630 return -1;
631 }
632
633 int unix_connect_opts(QemuOpts *opts)
634 {
635 fprintf(stderr, "unix sockets are not available on windows\n");
636 return -1;
637 }
638
639 int unix_listen(const char *path, char *ostr, int olen)
640 {
641 fprintf(stderr, "unix sockets are not available on windows\n");
642 return -1;
643 }
644
645 int unix_connect(const char *path)
646 {
647 fprintf(stderr, "unix sockets are not available on windows\n");
648 return -1;
649 }
650
651 #endif
652
653 #ifdef _WIN32
654 static void socket_cleanup(void)
655 {
656 WSACleanup();
657 }
658 #endif
659
660 int socket_init(void)
661 {
662 #ifdef _WIN32
663 WSADATA Data;
664 int ret, err;
665
666 ret = WSAStartup(MAKEWORD(2,2), &Data);
667 if (ret != 0) {
668 err = WSAGetLastError();
669 fprintf(stderr, "WSAStartup: %d\n", err);
670 return -1;
671 }
672 atexit(socket_cleanup);
673 #endif
674 return 0;
675 }
Something went wrong with that request. Please try again.