1 /*- 2 * Copyright (c) 2017 Maksym Sobolyev <sobomax@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 /* 28 * The test that setups two processes A and B and make A sending 29 * B UDP packet(s) and B send it back. The time of sending is recorded 30 * in the payload and time of the arrival is either determined by 31 * reading clock after recv() completes or using kernel-supplied 32 * via recvmsg(). End-to-end time t(A->B->A) is then calculated 33 * and compared against time for both t(A->B) + t(B->A) to make 34 * sure it makes sense. 35 */ 36 37 #include <sys/cdefs.h> 38 __FBSDID("$FreeBSD$"); 39 40 #include <sys/types.h> 41 #include <sys/socket.h> 42 #include <sys/wait.h> 43 #include <sys/time.h> 44 #include <netinet/in.h> 45 #include <arpa/inet.h> 46 #include <err.h> 47 #include <poll.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <strings.h> 52 #include <time.h> 53 #include <unistd.h> 54 55 #define NPKTS 1000 56 #define PKT_SIZE 128 57 /* Timeout to receive pong on the side A, 100ms */ 58 #define SRECV_TIMEOUT (1 * 100) 59 /* 60 * Timeout to receive ping on the side B. 4x as large as on the side A, 61 * so that in the case of packet loss the side A will have a chance to 62 * realize that and send few more before B bails out. 63 */ 64 #define RRECV_TIMEOUT (SRECV_TIMEOUT * 4) 65 #define MIN_NRECV ((NPKTS * 99) / 100) /* 99% */ 66 67 //#define SIMULATE_PLOSS 68 69 struct trip_ts { 70 struct timespec sent; 71 struct timespec recvd; 72 }; 73 74 struct test_pkt { 75 int pnum; 76 struct trip_ts tss[2]; 77 int lost; 78 unsigned char data[PKT_SIZE]; 79 }; 80 81 struct test_ctx { 82 const char *name; 83 int fds[2]; 84 struct pollfd pfds[2]; 85 union { 86 struct sockaddr_in v4; 87 struct sockaddr_in6 v6; 88 } sin[2]; 89 struct test_pkt test_pkts[NPKTS]; 90 int nsent; 91 int nrecvd; 92 clockid_t clock; 93 int use_recvmsg; 94 int ts_type; 95 }; 96 97 struct rtt { 98 struct timespec a2b; 99 struct timespec b2a; 100 struct timespec e2e; 101 struct timespec a2b_b2a; 102 }; 103 104 #define SEC(x) ((x)->tv_sec) 105 #define NSEC(x) ((x)->tv_nsec) 106 #define NSEC_MAX 1000000000L 107 #define NSEC_IN_USEC 1000L 108 109 #define timeval2timespec(tv, ts) \ 110 do { \ 111 SEC(ts) = (tv)->tv_sec; \ 112 NSEC(ts) = (tv)->tv_usec * NSEC_IN_USEC; \ 113 } while (0); 114 115 static const struct timespec zero_ts; 116 /* 0.01s, should be more than enough for the loopback communication */ 117 static const struct timespec max_ts = {.tv_nsec = (NSEC_MAX / 100)}; 118 119 enum ts_types {TT_TIMESTAMP = -2, TT_BINTIME = -1, 120 TT_REALTIME_MICRO = SO_TS_REALTIME_MICRO, TT_TS_BINTIME = SO_TS_BINTIME, 121 TT_REALTIME = SO_TS_REALTIME, TT_MONOTONIC = SO_TS_MONOTONIC}; 122 123 static clockid_t 124 get_clock_type(struct test_ctx *tcp) 125 { 126 switch (tcp->ts_type) { 127 case TT_TIMESTAMP: 128 case TT_BINTIME: 129 case TT_REALTIME_MICRO: 130 case TT_TS_BINTIME: 131 case TT_REALTIME: 132 return (CLOCK_REALTIME); 133 134 case TT_MONOTONIC: 135 return (CLOCK_MONOTONIC); 136 } 137 abort(); 138 } 139 140 static int 141 get_scm_type(struct test_ctx *tcp) 142 { 143 switch (tcp->ts_type) { 144 case TT_TIMESTAMP: 145 case TT_REALTIME_MICRO: 146 return (SCM_TIMESTAMP); 147 148 case TT_BINTIME: 149 case TT_TS_BINTIME: 150 return (SCM_BINTIME); 151 152 case TT_REALTIME: 153 return (SCM_REALTIME); 154 155 case TT_MONOTONIC: 156 return (SCM_MONOTONIC); 157 } 158 abort(); 159 } 160 161 static size_t 162 get_scm_size(struct test_ctx *tcp) 163 { 164 switch (tcp->ts_type) { 165 case TT_TIMESTAMP: 166 case TT_REALTIME_MICRO: 167 return (sizeof(struct timeval)); 168 169 case TT_BINTIME: 170 case TT_TS_BINTIME: 171 return (sizeof(struct bintime)); 172 173 case TT_REALTIME: 174 case TT_MONOTONIC: 175 return (sizeof(struct timespec)); 176 } 177 abort(); 178 } 179 180 static void 181 setup_ts_sockopt(struct test_ctx *tcp, int fd) 182 { 183 int rval, oname1, oname2, sval1, sval2; 184 185 oname1 = SO_TIMESTAMP; 186 oname2 = -1; 187 sval2 = -1; 188 189 switch (tcp->ts_type) { 190 case TT_REALTIME_MICRO: 191 case TT_TS_BINTIME: 192 case TT_REALTIME: 193 case TT_MONOTONIC: 194 oname2 = SO_TS_CLOCK; 195 sval2 = tcp->ts_type; 196 break; 197 198 case TT_TIMESTAMP: 199 break; 200 201 case TT_BINTIME: 202 oname1 = SO_BINTIME; 203 break; 204 205 default: 206 abort(); 207 } 208 209 sval1 = 1; 210 rval = setsockopt(fd, SOL_SOCKET, oname1, &sval1, 211 sizeof(sval1)); 212 if (rval != 0) { 213 err(1, "%s: setup_udp: setsockopt(%d, %d, 1)", tcp->name, 214 fd, oname1); 215 } 216 if (oname2 == -1) 217 return; 218 rval = setsockopt(fd, SOL_SOCKET, oname2, &sval2, 219 sizeof(sval2)); 220 if (rval != 0) { 221 err(1, "%s: setup_udp: setsockopt(%d, %d, %d)", 222 tcp->name, fd, oname2, sval2); 223 } 224 } 225 226 227 static void 228 setup_udp(struct test_ctx *tcp) 229 { 230 int i; 231 socklen_t sin_len, af_len; 232 233 af_len = sizeof(tcp->sin[0].v4); 234 for (i = 0; i < 2; i++) { 235 tcp->sin[i].v4.sin_len = af_len; 236 tcp->sin[i].v4.sin_family = AF_INET; 237 tcp->sin[i].v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 238 tcp->fds[i] = socket(PF_INET, SOCK_DGRAM, 0); 239 if (tcp->fds[i] < 0) 240 err(1, "%s: setup_udp: socket", tcp->name); 241 if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0) 242 err(1, "%s: setup_udp: bind(%s, %d)", tcp->name, 243 inet_ntoa(tcp->sin[i].v4.sin_addr), 0); 244 sin_len = af_len; 245 if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0) 246 err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]); 247 if (tcp->use_recvmsg != 0) { 248 setup_ts_sockopt(tcp, tcp->fds[i]); 249 } 250 251 tcp->pfds[i].fd = tcp->fds[i]; 252 tcp->pfds[i].events = POLLIN; 253 } 254 255 if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0) 256 err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, 257 inet_ntoa(tcp->sin[1].v4.sin_addr), ntohs(tcp->sin[1].v4.sin_port)); 258 if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0) 259 err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, 260 inet_ntoa(tcp->sin[0].v4.sin_addr), ntohs(tcp->sin[0].v4.sin_port)); 261 } 262 263 static char * 264 inet_ntoa6(const void *sin6_addr) 265 { 266 static char straddr[INET6_ADDRSTRLEN]; 267 268 inet_ntop(AF_INET6, sin6_addr, straddr, sizeof(straddr)); 269 return (straddr); 270 } 271 272 static void 273 setup_udp6(struct test_ctx *tcp) 274 { 275 int i; 276 socklen_t sin_len, af_len; 277 278 af_len = sizeof(tcp->sin[0].v6); 279 for (i = 0; i < 2; i++) { 280 tcp->sin[i].v6.sin6_len = af_len; 281 tcp->sin[i].v6.sin6_family = AF_INET6; 282 tcp->sin[i].v6.sin6_addr = in6addr_loopback; 283 tcp->fds[i] = socket(PF_INET6, SOCK_DGRAM, 0); 284 if (tcp->fds[i] < 0) 285 err(1, "%s: setup_udp: socket", tcp->name); 286 if (bind(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], af_len) < 0) 287 err(1, "%s: setup_udp: bind(%s, %d)", tcp->name, 288 inet_ntoa6(&tcp->sin[i].v6.sin6_addr), 0); 289 sin_len = af_len; 290 if (getsockname(tcp->fds[i], (struct sockaddr *)&tcp->sin[i], &sin_len) < 0) 291 err(1, "%s: setup_udp: getsockname(%d)", tcp->name, tcp->fds[i]); 292 if (tcp->use_recvmsg != 0) { 293 setup_ts_sockopt(tcp, tcp->fds[i]); 294 } 295 296 tcp->pfds[i].fd = tcp->fds[i]; 297 tcp->pfds[i].events = POLLIN; 298 } 299 300 if (connect(tcp->fds[0], (struct sockaddr *)&tcp->sin[1], af_len) < 0) 301 err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, 302 inet_ntoa6(&tcp->sin[1].v6.sin6_addr), 303 ntohs(tcp->sin[1].v6.sin6_port)); 304 if (connect(tcp->fds[1], (struct sockaddr *)&tcp->sin[0], af_len) < 0) 305 err(1, "%s: setup_udp: connect(%s, %d)", tcp->name, 306 inet_ntoa6(&tcp->sin[0].v6.sin6_addr), 307 ntohs(tcp->sin[0].v6.sin6_port)); 308 } 309 310 static void 311 teardown_udp(struct test_ctx *tcp) 312 { 313 314 close(tcp->fds[0]); 315 close(tcp->fds[1]); 316 } 317 318 static void 319 send_pkt(struct test_ctx *tcp, int pnum, int fdidx, const char *face) 320 { 321 ssize_t r; 322 size_t slen; 323 324 slen = sizeof(tcp->test_pkts[pnum]); 325 clock_gettime(get_clock_type(tcp), &tcp->test_pkts[pnum].tss[fdidx].sent); 326 r = send(tcp->fds[fdidx], &tcp->test_pkts[pnum], slen, 0); 327 if (r < 0) { 328 err(1, "%s: %s: send(%d)", tcp->name, face, tcp->fds[fdidx]); 329 } 330 if (r < (ssize_t)slen) { 331 errx(1, "%s: %s: send(%d): short send", tcp->name, face, 332 tcp->fds[fdidx]); 333 } 334 tcp->nsent += 1; 335 } 336 337 #define PDATA(tcp, i) ((tcp)->test_pkts[(i)].data) 338 339 static void 340 hdr_extract_ts(struct test_ctx *tcp, struct msghdr *mhp, struct timespec *tp) 341 { 342 int scm_type; 343 size_t scm_size; 344 union { 345 struct timespec ts; 346 struct bintime bt; 347 struct timeval tv; 348 } tdata; 349 struct cmsghdr *cmsg; 350 351 scm_type = get_scm_type(tcp); 352 scm_size = get_scm_size(tcp); 353 for (cmsg = CMSG_FIRSTHDR(mhp); cmsg != NULL; 354 cmsg = CMSG_NXTHDR(mhp, cmsg)) { 355 if ((cmsg->cmsg_level == SOL_SOCKET) && 356 (cmsg->cmsg_type == scm_type)) { 357 memcpy(&tdata, CMSG_DATA(cmsg), scm_size); 358 break; 359 } 360 } 361 if (cmsg == NULL) { 362 abort(); 363 } 364 switch (tcp->ts_type) { 365 case TT_REALTIME: 366 case TT_MONOTONIC: 367 *tp = tdata.ts; 368 break; 369 370 case TT_TIMESTAMP: 371 case TT_REALTIME_MICRO: 372 timeval2timespec(&tdata.tv, tp); 373 break; 374 375 case TT_BINTIME: 376 case TT_TS_BINTIME: 377 bintime2timespec(&tdata.bt, tp); 378 break; 379 380 default: 381 abort(); 382 } 383 } 384 385 static void 386 recv_pkt_recvmsg(struct test_ctx *tcp, int fdidx, const char *face, void *buf, 387 size_t rlen, struct timespec *tp) 388 { 389 /* We use a union to make sure hdr is aligned */ 390 union { 391 struct cmsghdr hdr; 392 unsigned char buf[CMSG_SPACE(1024)]; 393 } cmsgbuf; 394 struct msghdr msg; 395 struct iovec iov; 396 ssize_t rval; 397 398 memset(&msg, '\0', sizeof(msg)); 399 iov.iov_base = buf; 400 iov.iov_len = rlen; 401 msg.msg_iov = &iov; 402 msg.msg_iovlen = 1; 403 msg.msg_control = cmsgbuf.buf; 404 msg.msg_controllen = sizeof(cmsgbuf.buf); 405 406 rval = recvmsg(tcp->fds[fdidx], &msg, 0); 407 if (rval < 0) { 408 err(1, "%s: %s: recvmsg(%d)", tcp->name, face, tcp->fds[fdidx]); 409 } 410 if (rval < (ssize_t)rlen) { 411 errx(1, "%s: %s: recvmsg(%d): short recv", tcp->name, face, 412 tcp->fds[fdidx]); 413 } 414 415 hdr_extract_ts(tcp, &msg, tp); 416 } 417 418 static void 419 recv_pkt_recv(struct test_ctx *tcp, int fdidx, const char *face, void *buf, 420 size_t rlen, struct timespec *tp) 421 { 422 ssize_t rval; 423 424 rval = recv(tcp->fds[fdidx], buf, rlen, 0); 425 clock_gettime(get_clock_type(tcp), tp); 426 if (rval < 0) { 427 err(1, "%s: %s: recv(%d)", tcp->name, face, tcp->fds[fdidx]); 428 } 429 if (rval < (ssize_t)rlen) { 430 errx(1, "%s: %s: recv(%d): short recv", tcp->name, face, 431 tcp->fds[fdidx]); 432 } 433 } 434 435 static int 436 recv_pkt(struct test_ctx *tcp, int fdidx, const char *face, int tout) 437 { 438 int pr; 439 struct test_pkt recv_buf; 440 size_t rlen; 441 442 pr = poll(&tcp->pfds[fdidx], 1, tout); 443 if (pr < 0) { 444 err(1, "%s: %s: poll(%d)", tcp->name, face, tcp->fds[fdidx]); 445 } 446 if (pr == 0) { 447 return (-1); 448 } 449 if(tcp->pfds[fdidx].revents != POLLIN) { 450 errx(1, "%s: %s: poll(%d): unexpected result", tcp->name, face, 451 tcp->fds[fdidx]); 452 } 453 rlen = sizeof(recv_buf); 454 if (tcp->use_recvmsg == 0) { 455 recv_pkt_recv(tcp, fdidx, face, &recv_buf, rlen, 456 &recv_buf.tss[fdidx].recvd); 457 } else { 458 recv_pkt_recvmsg(tcp, fdidx, face, &recv_buf, rlen, 459 &recv_buf.tss[fdidx].recvd); 460 } 461 if (recv_buf.pnum < 0 || recv_buf.pnum >= NPKTS || 462 memcmp(recv_buf.data, PDATA(tcp, recv_buf.pnum), PKT_SIZE) != 0) { 463 errx(1, "%s: %s: recv(%d): corrupted data, packet %d", tcp->name, 464 face, tcp->fds[fdidx], recv_buf.pnum); 465 } 466 tcp->nrecvd += 1; 467 memcpy(tcp->test_pkts[recv_buf.pnum].tss, recv_buf.tss, 468 sizeof(recv_buf.tss)); 469 tcp->test_pkts[recv_buf.pnum].lost = 0; 470 return (recv_buf.pnum); 471 } 472 473 static void 474 test_server(struct test_ctx *tcp) 475 { 476 int i, j; 477 478 for (i = 0; i < NPKTS; i++) { 479 send_pkt(tcp, i, 0, __FUNCTION__); 480 j = recv_pkt(tcp, 0, __FUNCTION__, SRECV_TIMEOUT); 481 if (j < 0) { 482 warnx("packet %d is lost", i); 483 /* timeout */ 484 continue; 485 } 486 } 487 } 488 489 static void 490 test_client(struct test_ctx *tcp) 491 { 492 int i, j; 493 494 for (i = 0; i < NPKTS; i++) { 495 j = recv_pkt(tcp, 1, __FUNCTION__, RRECV_TIMEOUT); 496 if (j < 0) { 497 /* timeout */ 498 return; 499 } 500 #if defined(SIMULATE_PLOSS) 501 if ((i % 99) == 0) { 502 warnx("dropping packet %d", i); 503 continue; 504 } 505 #endif 506 send_pkt(tcp, j, 1, __FUNCTION__); 507 } 508 } 509 510 static void 511 calc_rtt(struct test_pkt *tpp, struct rtt *rttp) 512 { 513 514 timespecsub(&tpp->tss[1].recvd, &tpp->tss[0].sent, &rttp->a2b); 515 timespecsub(&tpp->tss[0].recvd, &tpp->tss[1].sent, &rttp->b2a); 516 timespecadd(&rttp->a2b, &rttp->b2a, &rttp->a2b_b2a); 517 timespecsub(&tpp->tss[0].recvd, &tpp->tss[0].sent, &rttp->e2e); 518 } 519 520 static void 521 test_run(int ts_type, int use_ipv6, int use_recvmsg, const char *name) 522 { 523 struct test_ctx test_ctx; 524 pid_t pid, cpid; 525 int i, j, status; 526 527 printf("Testing %s via %s: ", name, (use_ipv6 == 0) ? "IPv4" : "IPv6"); 528 fflush(stdout); 529 bzero(&test_ctx, sizeof(test_ctx)); 530 test_ctx.name = name; 531 test_ctx.use_recvmsg = use_recvmsg; 532 test_ctx.ts_type = ts_type; 533 if (use_ipv6 == 0) { 534 setup_udp(&test_ctx); 535 } else { 536 setup_udp6(&test_ctx); 537 } 538 for (i = 0; i < NPKTS; i++) { 539 test_ctx.test_pkts[i].pnum = i; 540 test_ctx.test_pkts[i].lost = 1; 541 for (j = 0; j < PKT_SIZE; j++) { 542 test_ctx.test_pkts[i].data[j] = (unsigned char)random(); 543 } 544 } 545 cpid = fork(); 546 if (cpid < 0) { 547 err(1, "%s: fork()", test_ctx.name); 548 } 549 if (cpid == 0) { 550 test_client(&test_ctx); 551 exit(0); 552 } 553 test_server(&test_ctx); 554 pid = waitpid(cpid, &status, 0); 555 if (pid == (pid_t)-1) { 556 err(1, "%s: waitpid(%d)", test_ctx.name, cpid); 557 } 558 559 if (WIFEXITED(status)) { 560 if (WEXITSTATUS(status) != EXIT_SUCCESS) { 561 errx(1, "client exit status is %d", 562 WEXITSTATUS(status)); 563 } 564 } else { 565 if (WIFSIGNALED(status)) 566 errx(1, "abnormal termination of client, signal %d%s", 567 WTERMSIG(status), WCOREDUMP(status) ? 568 " (core file generated)" : ""); 569 else 570 errx(1, "termination of client, unknown status"); 571 } 572 if (test_ctx.nrecvd < MIN_NRECV) { 573 errx(1, "packet loss is too high %d received out of %d, min %d", 574 test_ctx.nrecvd, test_ctx.nsent, MIN_NRECV); 575 } 576 for (i = 0; i < NPKTS; i++) { 577 struct rtt rtt; 578 if (test_ctx.test_pkts[i].lost != 0) { 579 continue; 580 } 581 calc_rtt(&test_ctx.test_pkts[i], &rtt); 582 if (!timespeccmp(&rtt.e2e, &rtt.a2b_b2a, >)) 583 errx(1, "end-to-end trip time is too small"); 584 if (!timespeccmp(&rtt.e2e, &max_ts, <)) 585 errx(1, "end-to-end trip time is too large"); 586 if (!timespeccmp(&rtt.a2b, &zero_ts, >)) 587 errx(1, "A2B trip time is not positive"); 588 if (!timespeccmp(&rtt.b2a, &zero_ts, >)) 589 errx(1, "B2A trip time is not positive"); 590 } 591 teardown_udp(&test_ctx); 592 } 593 594 int 595 main(void) 596 { 597 int i; 598 599 srandomdev(); 600 601 for (i = 0; i < 2; i++) { 602 test_run(0, i, 0, "send()/recv()"); 603 printf("OK\n"); 604 test_run(TT_TIMESTAMP, i, 1, 605 "send()/recvmsg(), setsockopt(SO_TIMESTAMP, 1)"); 606 printf("OK\n"); 607 if (i == 0) { 608 test_run(TT_BINTIME, i, 1, 609 "send()/recvmsg(), setsockopt(SO_BINTIME, 1)"); 610 printf("OK\n"); 611 } 612 test_run(TT_REALTIME_MICRO, i, 1, 613 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME_MICRO)"); 614 printf("OK\n"); 615 test_run(TT_TS_BINTIME, i, 1, 616 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_BINTIME)"); 617 printf("OK\n"); 618 test_run(TT_REALTIME, i, 1, 619 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_REALTIME)"); 620 printf("OK\n"); 621 test_run(TT_MONOTONIC, i, 1, 622 "send()/recvmsg(), setsockopt(SO_TS_CLOCK, SO_TS_MONOTONIC)"); 623 printf("OK\n"); 624 } 625 exit(0); 626 } 627