fsm.c revision 171568
1/*- 2 * Copyright (c) 2005-2007 Daniel Braniss <danny@cs.huji.ac.il> 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/* 29 | $Id: fsm.c,v 2.8 2007/05/19 16:34:21 danny Exp danny $ 30 */ 31 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD: head/sbin/iscontrol/fsm.c 171568 2007-07-24 15:35:02Z scottl $"); 34 35#include <sys/param.h> 36#include <sys/types.h> 37#include <sys/socket.h> 38#include <sys/sysctl.h> 39 40#include <netinet/in.h> 41#include <netinet/tcp.h> 42#include <arpa/inet.h> 43#if __FreeBSD_version < 500000 44#include <sys/time.h> 45#endif 46#include <sys/ioctl.h> 47#include <netdb.h> 48#include <stdlib.h> 49#include <unistd.h> 50#include <stdio.h> 51#include <string.h> 52#include <errno.h> 53#include <fcntl.h> 54#include <time.h> 55#include <syslog.h> 56#include <stdarg.h> 57#include <camlib.h> 58 59#include "iscsi.h" 60#include "iscontrol.h" 61#include "pdu.h" 62 63typedef enum { 64 T1 = 1, 65 T2, /*T3,*/ T4, T5, /*T6,*/ T7, T8, T9, 66 T10, T11, T12, T13, T14, T15, T16, T18 67} trans_t; 68 69static trans_t 70tcpConnect(isess_t *sess) 71{ 72 isc_opt_t *op = sess->op; 73 int val, sv_errno; 74 struct addrinfo *res, hints; 75 struct sockaddr_in sn; 76 struct in_addr ipn; 77 time_t sec; 78 79 debug_called(3); 80 if(sess->flags & (SESS_RECONNECT|SESS_REDIRECT)) { 81 syslog(LOG_INFO, "%s", (sess->flags & SESS_RECONNECT) 82 ? "Reconnect": "Redirected"); 83 84 debug(3, "%s", (sess->flags & SESS_RECONNECT) ? "Reconnect": "Redirected"); 85 shutdown(sess->soc, SHUT_RDWR); 86 //close(sess->soc); 87 sleep(5); // XXX: actually should be ? 88 sess->soc = -1; 89 90 sess->flags &= ~SESS_CONNECTED; 91 if(sess->flags & SESS_REDIRECT) { 92 if(sess->redirect_cnt++ > MAXREDIRECTS) { 93 syslog(LOG_WARNING, "too many redirects > %d", MAXREDIRECTS); 94 return 0; 95 } 96 sess->flags |= SESS_RECONNECT; 97 } 98 if((sess->flags & SESS_RECONNECT) == 0) 99 return 0; 100 101 // make sure we are not in a loop 102 // XXX: this code has to be tested 103 sec = time(0) - sess->reconnect_time; 104 if(sec > (5*60)) { 105 // if we've been connected for more that 5 minutes 106 // then just reconnect 107 sess->reconnect_time = sec; 108 sess->reconnect_cnt1 = 0; 109 } 110 else { 111 // 112 sess->reconnect_cnt1++; 113 if((sec / sess->reconnect_cnt1) < 2) { 114 // if less that 2 seconds from the last reconnect 115 // we are most probably looping 116 syslog(LOG_CRIT, "too many reconnects %d", sess->reconnect_cnt1); 117 return 0; 118 } 119 } 120 sess->reconnect_cnt++; 121 // sess->flags &= ~(SESS_RECONNECT|SESS_REDIRECT); 122 } 123 124 if((sess->soc = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 125 fprintf(stderr, "tcpConnect: socket: %m"); 126 return 0; 127 } 128 129 memset(&hints, 0, sizeof(hints)); 130 hints.ai_family = PF_INET; 131 hints.ai_socktype = SOCK_STREAM; 132 133 debug(3, "targetAddress=%s port=%d", op->targetAddress, op->port); 134 if(inet_aton(op->targetAddress, &ipn)) 135 hints.ai_flags |= AI_NUMERICHOST; 136 if((val = getaddrinfo(op->targetAddress, NULL, &hints, &res)) != 0) { 137 fprintf(stderr, "getaddrinfo(%s): %s\n", op->targetAddress, gai_strerror(val)); 138 return 0; 139 } 140 memcpy(&sn, res->ai_addr, sizeof(struct sockaddr_in)); 141 sn.sin_port = htons(op->port); 142 freeaddrinfo(res); 143 144 // from Patrick.Guelat@imp.ch: 145 // iscontrol can be called without waiting for the socket entry to time out 146 val = 1; 147 if(setsockopt(sess->soc, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) < 0) { 148 fprintf(stderr, "Cannot set socket SO_REUSEADDR %d: %s\n\n", 149 errno, strerror(errno)); 150 } 151 152 sess->flags &= ~SESS_CONNECTED; 153 154 if(connect(sess->soc, (struct sockaddr *)&sn, sizeof(struct sockaddr_in)) != -1) { 155#if 0 156 struct timeval timeout; 157 158 val = 1; 159 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) 160 fprintf(stderr, "Cannot set socket KEEPALIVE option err=%d %s\n", 161 errno, strerror(errno)); 162 163 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0) 164 fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n", 165 errno, strerror(errno)); 166 167 timeout.tv_sec = 10; 168 timeout.tv_usec = 0; 169 if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) 170 || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)) { 171 fprintf(stderr, "Cannot set socket timeout to %ld err=%d %s\n", 172 timeout.tv_sec, errno, strerror(errno)); 173 } 174#endif 175#ifdef CURIOUS 176 { 177 int len = sizeof(val); 178 if(getsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0) 179 fprintf(stderr, "was: SO_SNDBUF=%dK\n", val/1024); 180 } 181#endif 182 if(sess->op->sockbufsize) { 183 val = sess->op->sockbufsize * 1024; 184 if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0) 185 || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)) { 186 fprintf(stderr, "Cannot set socket sndbuf & rcvbuf to %d err=%d %s\n", 187 val, errno, strerror(errno)); 188 return 0; 189 } 190 } 191 sess->flags |= SESS_CONNECTED; 192 return T1; 193 194 } 195 sv_errno = errno; 196 fprintf(stderr, "errno=%d\n", sv_errno); 197 perror("connect"); 198 switch(sv_errno) { 199 case ECONNREFUSED: 200 case ENETUNREACH: 201 case ETIMEDOUT: 202 sleep(5); // for now ... 203 return T1; 204 default: 205 return 0; // terminal error 206 } 207 208} 209 210int 211setOptions(isess_t *sess, int flag) 212{ 213 isc_opt_t oop; 214 char *sep; 215 216 debug_called(3); 217 218 bzero(&oop, sizeof(isc_opt_t)); 219 220 if((flag & SESS_FULLFEATURE) == 0) { 221 oop.initiatorName = sess->op->initiatorName; 222 oop.targetAddress = sess->op->targetAddress; 223 if(sess->op->targetName != 0) 224 oop.targetName = sess->op->targetName; 225 226 oop.maxRecvDataSegmentLength = sess->op->maxRecvDataSegmentLength; 227 oop.maxXmitDataSegmentLength = sess->op->maxXmitDataSegmentLength; // XXX: 228 oop.maxBurstLength = sess->op->maxBurstLength; 229 oop.maxluns = sess->op->maxluns; 230 } 231 else { 232 /* 233 | turn on digestion only after login 234 */ 235 if(sess->op->headerDigest != NULL) { 236 sep = strchr(sess->op->headerDigest, ','); 237 if(sep == NULL) 238 oop.headerDigest = sess->op->headerDigest; 239 debug(1, "oop.headerDigest=%s", oop.headerDigest); 240 } 241 if(sess->op->dataDigest != NULL) { 242 sep = strchr(sess->op->dataDigest, ','); 243 if(sep == NULL) 244 oop.dataDigest = sess->op->dataDigest; 245 debug(1, "oop.dataDigest=%s", oop.dataDigest); 246 } 247 } 248 249 if(ioctl(sess->fd, ISCSISETOPT, &oop)) { 250 perror("ISCSISETOPT"); 251 return -1; 252 } 253 return 0; 254} 255 256static trans_t 257startSession(isess_t *sess) 258{ 259 260 int n, fd, nfd; 261 char *dev; 262 263 debug_called(3); 264 265 if((sess->flags & SESS_CONNECTED) == 0) { 266 return T2; 267 } 268 if(sess->fd == -1) { 269 fd = open(iscsidev, O_RDWR); 270 if(fd < 0) { 271 perror(iscsidev); 272 return 0; 273 } 274 { 275 // XXX: this has to go 276 size_t n; 277 n = sizeof(sess->isid); 278 if(sysctlbyname("net.iscsi.isid", (void *)sess->isid, (size_t *)&n, 0, 0) != 0) 279 perror("sysctlbyname"); 280 } 281 if(ioctl(fd, ISCSISETSES, &n)) { 282 perror("ISCSISETSES"); 283 return 0; 284 } 285 asprintf(&dev, "%s%d", iscsidev, n); 286 nfd = open(dev, O_RDWR); 287 if(nfd < 0) { 288 perror(dev); 289 free(dev); 290 return 0; 291 } 292 free(dev); 293 close(fd); 294 sess->fd = nfd; 295 296 if(setOptions(sess, 0) != 0) 297 return -1; 298 } 299 300 if(ioctl(sess->fd, ISCSISETSOC, &sess->soc)) { 301 perror("ISCSISETSOC"); 302 return 0; 303 } 304 305 return T4; 306} 307 308isess_t *currsess; 309 310static void 311trap(int sig) 312{ 313 syslog(LOG_NOTICE, "trapped signal %d", sig); 314 fprintf(stderr, "trapped signal %d\n", sig); 315 316 switch(sig) { 317 case SIGHUP: 318 currsess->flags |= SESS_DISCONNECT; 319 break; 320 321 case SIGUSR1: 322 currsess->flags |= SESS_RECONNECT; 323 break; 324 325 case SIGINT: 326 case SIGTERM: 327 default: 328 return; // ignore 329 } 330} 331 332static void 333doCAM(isess_t *sess) 334{ 335 char pathstr[1024]; 336 union ccb *ccb; 337 int i; 338 339 if(ioctl(sess->fd, ISCSIGETCAM, &sess->cam) != 0) { 340 syslog(LOG_WARNING, "ISCSIGETCAM failed: %d", errno); 341 return; 342 } 343 debug(2, "nluns=%d", sess->cam.target_nluns); 344 /* 345 | for now will do this for each lun ... 346 */ 347 for(i = 0; i < sess->cam.target_nluns; i++) { 348 debug(2, "CAM path_id=%d target_id=%d target_lun=%d", 349 sess->cam.path_id, sess->cam.target_id, sess->cam.target_lun[i]); 350 351 sess->camdev = cam_open_btl(sess->cam.path_id, sess->cam.target_id, 352 sess->cam.target_lun[i], O_RDWR, NULL); 353 if(sess->camdev == NULL) { 354 syslog(LOG_WARNING, "%s", cam_errbuf); 355 debug(3, "%s", cam_errbuf); 356 continue; 357 } 358 359 cam_path_string(sess->camdev, pathstr, sizeof(pathstr)); 360 debug(2, "pathstr=%s", pathstr); 361 362 ccb = cam_getccb(sess->camdev); 363 bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr)); 364 ccb->ccb_h.func_code = XPT_REL_SIMQ; 365 ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS; 366 ccb->crs.openings = sess->op->tags; 367 368 if(cam_send_ccb(sess->camdev, ccb) < 0) 369 syslog(LOG_WARNING, "%s", cam_errbuf); 370 else 371 if((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 372 syslog(LOG_WARNING, "XPT_REL_SIMQ CCB failed"); 373 // cam_error_print(sess->camdev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr); 374 } 375 else 376 syslog(LOG_INFO, "%s tagged openings now %d\n", pathstr, ccb->crs.openings); 377 378 cam_freeccb(ccb); 379 cam_close_device(sess->camdev); 380 } 381} 382 383static trans_t 384supervise(isess_t *sess) 385{ 386 int sig, val; 387 388 debug_called(3); 389 390 if(strcmp(sess->op->sessionType, "Discovery") == 0) { 391 sess->flags |= SESS_DISCONNECT; 392 return T9; 393 } 394 395 if(vflag) 396 printf("ready to go scsi\n"); 397 398 if(setOptions(sess, SESS_FULLFEATURE) != 0) 399 return 0; // failure 400 401 if((sess->flags & SESS_FULLFEATURE) == 0) { 402 if(daemon(0, 1) != 0) { 403 perror("daemon"); 404 exit(1); 405 } 406 407 openlog("iscontrol", LOG_CONS|LOG_PERROR|LOG_PID|LOG_NDELAY, LOG_KERN); 408 syslog(LOG_INFO, "running"); 409 410 currsess = sess; 411 if(ioctl(sess->fd, ISCSISTART)) { 412 perror("ISCSISTART"); 413 return -1; 414 } 415 doCAM(sess); 416 417 } 418 else { 419 420 if(ioctl(sess->fd, ISCSIRESTART)) { 421 perror("ISCSIRESTART"); 422 return -1; 423 } 424 } 425 426 signal(SIGINT, trap); 427 signal(SIGHUP, trap); 428 signal(SIGTERM, trap); 429 430 sig = SIGUSR1; 431 signal(sig, trap); 432 if(ioctl(sess->fd, ISCSISIGNAL, &sig)) { 433 perror("ISCSISIGNAL"); 434 return -1; 435 } 436 sess->flags |= SESS_FULLFEATURE; 437 438 sess->flags &= ~(SESS_REDIRECT | SESS_RECONNECT); 439 printf("iscontrol: supervise starting main loop\n"); 440 /* 441 | the main loop - actually do nothing 442 | all the work is done inside the kernel 443 */ 444 while((sess->flags & (SESS_REDIRECT|SESS_RECONNECT|SESS_DISCONNECT)) == 0) { 445 // do something? 446 // like sending a nop_out? 447 sleep(60); 448 } 449 printf("iscontrol: supervise going down\n"); 450 syslog(LOG_INFO, "sess flags=%x", sess->flags); 451 452 sig = 0; 453 if(ioctl(sess->fd, ISCSISIGNAL, &sig)) { 454 perror("ISCSISIGNAL"); 455 } 456 457 if(sess->flags & SESS_DISCONNECT) { 458 val = 0; 459 if(ioctl(sess->fd, ISCSISTOP, &val)) { 460 perror("ISCSISTOP"); 461 } 462 sess->flags &= ~SESS_FULLFEATURE; 463 return T9; 464 } 465 else { 466 sess->flags |= SESS_INITIALLOGIN1; 467 } 468 return T8; 469} 470 471static int 472handledDiscoveryResp(isess_t *sess, pdu_t *pp) 473{ 474 u_char *ptr; 475 int len, n; 476 477 debug_called(3); 478 479 len = pp->ds_len; 480 ptr = pp->ds; 481 while(len > 0) { 482 if(*ptr != 0) 483 printf("%s\n", ptr); 484 n = strlen((char *)ptr) + 1; 485 len -= n; 486 ptr += n; 487 } 488 return 0; 489} 490 491static int 492doDiscovery(isess_t *sess) 493{ 494 pdu_t spp; 495 text_req_t *tp = (text_req_t *)&spp.ipdu.bhs; 496 497 debug_called(3); 498 499 bzero(&spp, sizeof(pdu_t)); 500 tp->cmd = ISCSI_TEXT_CMD /*| 0x40 */; // because of a bug in openiscsi-target 501 tp->F = 1; 502 tp->ttt = 0xffffffff; 503 addText(&spp, "SendTargets=All"); 504 return sendPDU(sess, &spp, handledDiscoveryResp); 505} 506 507static trans_t 508doLogin(isess_t *sess) 509{ 510 isc_opt_t *op = sess->op; 511 int status, count; 512 513 debug_called(3); 514 515 if(op->chapSecret == NULL && op->tgtChapSecret == NULL) 516 /* 517 | don't need any security negotiation 518 | or in other words: we don't have any secrets to exchange 519 */ 520 sess->csg = LON_PHASE; 521 else 522 sess->csg = SN_PHASE; 523 524 if(sess->tsih) { 525 sess->tsih = 0; // XXX: no 'reconnect' yet 526 sess->flags &= ~SESS_NEGODONE; // XXX: KLUDGE 527 } 528 count = 10; // should be more than enough 529 do { 530 debug(3, "count=%d csg=%d", count, sess->csg); 531 status = loginPhase(sess); 532 if(count-- == 0) 533 // just in case we get into a loop 534 status = -1; 535 } while(status == 0 && (sess->csg != FF_PHASE)); 536 537 sess->flags &= ~SESS_INITIALLOGIN; 538 debug(3, "status=%d", status); 539 540 switch(status) { 541 case 0: // all is ok ... 542 sess->flags |= SESS_LOGGEDIN; 543 if(strcmp(sess->op->sessionType, "Discovery") == 0) 544 doDiscovery(sess); 545 return T5; 546 547 case 1: // redirect - temporary/permanent 548 /* 549 | start from scratch? 550 */ 551 sess->flags &= ~SESS_NEGODONE; 552 sess->flags |= (SESS_REDIRECT | SESS_INITIALLOGIN1); 553 syslog(LOG_DEBUG, "target sent REDIRECT"); 554 return T7; 555 556 case 2: // initiator terminal error 557 case 3: // target terminal error -- could retry ... 558 default: 559 return 0; 560 } 561} 562 563static int 564handleLogoutResp(isess_t *sess, pdu_t *pp) 565{ 566 if(sess->flags & SESS_DISCONNECT) 567 return 0; 568 return T13; 569} 570 571static trans_t 572startLogout(isess_t *sess) 573{ 574 pdu_t spp; 575 logout_req_t *p = (logout_req_t *)&spp.ipdu.bhs; 576 577 bzero(&spp, sizeof(pdu_t)); 578 p->cmd = ISCSI_LOGOUT_CMD| 0x40; 579 p->reason = BIT(7) | 0; 580 p->CID = htons(1); 581 582 return sendPDU(sess, &spp, handleLogoutResp); 583} 584 585static trans_t 586inLogout(isess_t *sess) 587{ 588 if(sess->flags & SESS_RECONNECT) 589 return T18; 590 return 0; 591} 592 593typedef enum { 594 S1, S2, /*S3,*/ S4, S5, S6, S7, S8 595} state_t; 596 597#if 0 598 S1: FREE 599 S2: XPT_WAIT 600 S4: IN_LOGIN 601 S5: LOGGED_IN 602 S6: IN_LOGOUT 603 S7: LOGOUT_REQUESTED 604 S8: CLEANUP_WAIT 605 606 -------<-------------+ 607 +--------->/ S1 \<----+ | 608 T13| +->\ /<-+ \ | 609 | / ---+--- \ \ | 610 | / | T2 \ | | 611 | T8 | |T1 | | | 612 | | | / |T7 | 613 | | | / | | 614 | | | / | | 615 | | V / / | 616 | | ------- / / | 617 | | / S2 \ / | 618 | | \ / / | 619 | | ---+--- / | 620 | | |T4 / | 621 | | V / | T18 622 | | ------- / | 623 | | / S4 \ | 624 | | \ / | 625 | | ---+--- | T15 626 | | |T5 +--------+---------+ 627 | | | /T16+-----+------+ | 628 | | | / -+-----+--+ | | 629 | | | / / S7 \ |T12| | 630 | | | / +->\ /<-+ V V 631 | | | / / -+----- ------- 632 | | | / /T11 |T10 / S8 \ 633 | | V / / V +----+ \ / 634 | | ---+-+- ----+-- | ------- 635 | | / S5 \T9 / S6 \<+ ^ 636 | +-----\ /--->\ / T14 | 637 | ------- --+----+------+T17 638 +---------------------------+ 639#endif 640 641int 642fsm(isc_opt_t *op) 643{ 644 state_t state; 645 isess_t *sess; 646 647 if((sess = calloc(1, sizeof(isess_t))) == NULL) { 648 // boy, is this a bad start ... 649 fprintf(stderr, "no memory!\n"); 650 return -1; 651 } 652 653 state = S1; 654 sess->op = op; 655 sess->fd = -1; 656 sess->soc = -1; 657 658 sess->flags = SESS_INITIALLOGIN | SESS_INITIALLOGIN1; 659 660 do { 661 switch(state) { 662 663 case S1: 664 switch(tcpConnect(sess)) { 665 case T1: state = S2; break; 666 default: state = S8; break; 667 } 668 break; 669 670 case S2: 671 switch(startSession(sess)) { 672 case T2: state = S1; break; 673 case T4: state = S4; break; 674 default: state = S8; break; 675 } 676 break; 677 678 case S4: 679 switch(doLogin(sess)) { 680 case T7: state = S1; break; 681 case T5: state = S5; break; 682 default: state = S8; break; 683 } 684 break; 685 686 case S5: 687 switch(supervise(sess)) { 688 case T8: state = S1; break; 689 case T9: state = S6; break; 690 case T11: state = S7; break; 691 case T15: state = S8; break; 692 default: state = S8; break; 693 } 694 break; 695 696 case S6: 697 switch(startLogout(sess)) { 698 case T13: state = S1; break; 699 case T14: state = S6; break; 700 case T16: state = S8; break; 701 default: state = S8; break; 702 } 703 break; 704 705 case S7: 706 switch(inLogout(sess)) { 707 case T18: state = S1; break; 708 case T10: state = S6; break; 709 case T12: state = S7; break; 710 case T16: state = S8; break; 711 default: state = S8; break; 712 } 713 break; 714 715 case S8: 716 // maybe do some clean up? 717 syslog(LOG_INFO, "terminated"); 718 return 0; 719 } 720 } while(1); 721} 722