kernel - Cleanup macros
[dragonfly.git] / sbin / iscontrol / fsm.c
blob8c3b80016a27bcfb515b83cc48286c8e5a7926dc
1 /*-
2 * Copyright (c) 2005-2008 Daniel Braniss <danny@cs.huji.ac.il>
3 * All rights reserved.
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.
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.
29 | $Id: fsm.c,v 2.8 2007/05/19 16:34:21 danny Exp danny $
32 #include <sys/param.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/sysctl.h>
37 #include <netinet/in.h>
38 #include <netinet/tcp.h>
39 #include <arpa/inet.h>
40 #include <sys/ioctl.h>
41 #include <netdb.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <time.h>
49 #include <syslog.h>
50 #include <stdarg.h>
51 #include <camlib.h>
53 #include "iscsi.h"
54 #include "iscontrol.h"
56 typedef enum {
57 T1 = 1,
58 T2, /*T3,*/ T4, T5, /*T6,*/ T7, T8, T9,
59 T10, T11, T12, T13, T14, T15, T16, T18
60 } trans_t;
63 | now supports IPV6
64 | thanks to:
65 | Hajimu UMEMOTO @ Internet Mutual Aid Society Yokohama, Japan
66 | ume@mahoroba.org ume@{,jp.}FreeBSD.org
67 | http://www.imasy.org/~ume/
69 static trans_t
70 tcpConnect(isess_t *sess)
72 isc_opt_t *op = sess->op;
73 int val, sv_errno, soc;
74 struct addrinfo *res, *res0, hints;
75 char pbuf[10];
77 debug_called(3);
78 if(sess->flags & (SESS_RECONNECT|SESS_REDIRECT)) {
79 syslog(LOG_INFO, "%s", (sess->flags & SESS_RECONNECT)
80 ? "Reconnect": "Redirected");
82 debug(1, "%s", (sess->flags & SESS_RECONNECT) ? "Reconnect": "Redirected");
83 shutdown(sess->soc, SHUT_RDWR);
84 //close(sess->soc);
85 sess->soc = -1;
87 sess->flags &= ~SESS_CONNECTED;
88 if(sess->flags & SESS_REDIRECT) {
89 sess->redirect_cnt++;
90 sess->flags |= SESS_RECONNECT;
91 } else
92 sleep(2); // XXX: actually should be ?
93 #ifdef notyet
95 time_t sec;
96 // make sure we are not in a loop
97 // XXX: this code has to be tested
98 sec = time(0) - sess->reconnect_time;
99 if(sec > (5*60)) {
100 // if we've been connected for more that 5 minutes
101 // then just reconnect
102 sess->reconnect_time = sec;
103 sess->reconnect_cnt1 = 0;
105 else {
107 sess->reconnect_cnt1++;
108 if((sec / sess->reconnect_cnt1) < 2) {
109 // if less that 2 seconds from the last reconnect
110 // we are most probably looping
111 syslog(LOG_CRIT, "too many reconnects %d", sess->reconnect_cnt1);
112 return 0;
116 #endif
117 sess->reconnect_cnt++;
120 snprintf(pbuf, sizeof(pbuf), "%d", op->port);
121 memset(&hints, 0, sizeof(hints));
122 hints.ai_family = PF_UNSPEC;
123 hints.ai_socktype = SOCK_STREAM;
124 debug(1, "targetAddress=%s port=%d", op->targetAddress, op->port);
125 if((val = getaddrinfo(op->targetAddress, pbuf, &hints, &res0)) != 0) {
126 fprintf(stderr, "getaddrinfo(%s): %s\n", op->targetAddress, gai_strerror(val));
127 return 0;
129 sess->flags &= ~SESS_CONNECTED;
130 sv_errno = 0;
131 soc = -1;
132 for(res = res0; res; res = res->ai_next) {
133 soc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
134 if (soc == -1)
135 continue;
137 // from Patrick.Guelat@imp.ch:
138 // iscontrol can be called without waiting for the socket entry to time out
139 val = 1;
140 if(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) < 0) {
141 fprintf(stderr, "Cannot set socket SO_REUSEADDR %d: %s\n\n",
142 errno, strerror(errno));
144 if(connect(soc, res->ai_addr, res->ai_addrlen) == 0)
145 break;
147 sv_errno = errno;
148 close(soc);
149 soc = -1;
151 freeaddrinfo(res0);
153 if(soc != -1) {
154 sess->soc = soc;
156 /* Default to TCP_NODELAY to improve transfers */
157 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
158 fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
159 errno, strerror(errno));
161 #if 0
162 struct timeval timeout;
164 val = 1;
165 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0)
166 fprintf(stderr, "Cannot set socket KEEPALIVE option err=%d %s\n",
167 errno, strerror(errno));
169 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
170 fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
171 errno, strerror(errno));
173 timeout.tv_sec = 10;
174 timeout.tv_usec = 0;
175 if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
176 || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)) {
177 fprintf(stderr, "Cannot set socket timeout to %ld err=%d %s\n",
178 timeout.tv_sec, errno, strerror(errno));
180 #endif
181 #ifdef CURIOUS
183 int len = sizeof(val);
184 if(getsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0)
185 fprintf(stderr, "was: SO_SNDBUF=%dK\n", val/1024);
187 #endif
188 if(sess->op->sockbufsize) {
189 val = sess->op->sockbufsize * 1024;
190 if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
191 || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)) {
192 fprintf(stderr, "Cannot set socket sndbuf & rcvbuf to %d err=%d %s\n",
193 val, errno, strerror(errno));
194 return 0;
197 sess->flags |= SESS_CONNECTED;
198 return T1;
201 fprintf(stderr, "errno=%d\n", sv_errno);
202 perror("connect");
203 switch(sv_errno) {
204 case ECONNREFUSED:
205 case ENETUNREACH:
206 case ETIMEDOUT:
207 if((sess->flags & SESS_REDIRECT) == 0) {
208 if(strcmp(op->targetAddress, sess->target.address) != 0) {
209 syslog(LOG_INFO, "reconnecting to original target address");
210 free(op->targetAddress);
211 op->targetAddress = sess->target.address;
212 op->port = sess->target.port;
213 op->targetPortalGroupTag = sess->target.pgt;
214 return T1;
217 sleep(5); // for now ...
218 return T1;
219 default:
220 return 0; // terminal error
225 setOptions(isess_t *sess, int flag)
227 isc_opt_t oop;
228 char *sep;
230 debug_called(3);
232 bzero(&oop, sizeof(isc_opt_t));
234 if((flag & SESS_FULLFEATURE) == 0) {
235 oop.initiatorName = sess->op->initiatorName;
236 oop.targetAddress = sess->op->targetAddress;
237 if(sess->op->targetName != 0)
238 oop.targetName = sess->op->targetName;
240 oop.maxRecvDataSegmentLength = sess->op->maxRecvDataSegmentLength;
241 oop.maxXmitDataSegmentLength = sess->op->maxXmitDataSegmentLength; // XXX:
242 oop.maxBurstLength = sess->op->maxBurstLength;
243 oop.maxluns = sess->op->maxluns;
245 else {
247 | turn on digestion only after login
249 if(sess->op->headerDigest != NULL) {
250 sep = strchr(sess->op->headerDigest, ',');
251 if(sep == NULL)
252 oop.headerDigest = sess->op->headerDigest;
253 debug(1, "oop.headerDigest=%s", oop.headerDigest);
255 if(sess->op->dataDigest != NULL) {
256 sep = strchr(sess->op->dataDigest, ',');
257 if(sep == NULL)
258 oop.dataDigest = sess->op->dataDigest;
259 debug(1, "oop.dataDigest=%s", oop.dataDigest);
263 if(ioctl(sess->fd, ISCSISETOPT, &oop)) {
264 perror("ISCSISETOPT");
265 return -1;
267 return 0;
270 static trans_t
271 startSession(isess_t *sess)
274 int n, fd, nfd;
275 char *dev;
277 debug_called(3);
279 if((sess->flags & SESS_CONNECTED) == 0) {
280 return T2;
282 if(sess->fd == -1) {
283 fd = open(iscsidev, O_RDWR);
284 if(fd < 0) {
285 perror(iscsidev);
286 return 0;
289 // XXX: this has to go
290 size_t n;
291 n = sizeof(sess->isid);
292 if(sysctlbyname("net.iscsi.isid", (void *)sess->isid, (size_t *)&n, 0, 0) != 0)
293 perror("sysctlbyname");
295 if(ioctl(fd, ISCSISETSES, &n)) {
296 perror("ISCSISETSES");
297 return 0;
299 sleep(1); /* XXX temporary */
300 asprintf(&dev, "%s%d", iscsidev, n);
301 nfd = open(dev, O_RDWR);
302 if(nfd < 0) {
303 perror(dev);
304 free(dev);
305 return 0;
307 free(dev);
308 close(fd);
309 sess->fd = nfd;
311 if(setOptions(sess, 0) != 0)
312 return -1;
315 if(ioctl(sess->fd, ISCSISETSOC, &sess->soc)) {
316 perror("ISCSISETSOC");
317 return 0;
320 return T4;
323 isess_t *currsess;
325 static void
326 trap(int sig)
328 syslog(LOG_NOTICE, "trapped signal %d", sig);
329 fprintf(stderr, "trapped signal %d\n", sig);
331 switch(sig) {
332 case SIGHUP:
333 currsess->flags |= SESS_DISCONNECT;
334 break;
336 case SIGUSR1:
337 currsess->flags |= SESS_RECONNECT;
338 break;
340 case SIGINT:
341 case SIGTERM:
342 default:
343 return; // ignore
347 static void
348 doCAM(isess_t *sess)
350 char pathstr[1024];
351 union ccb *ccb;
352 int i;
354 if(ioctl(sess->fd, ISCSIGETCAM, &sess->cam) != 0) {
355 syslog(LOG_WARNING, "ISCSIGETCAM failed: %d", errno);
356 return;
358 debug(2, "nluns=%d", sess->cam.target_nluns);
360 | for now will do this for each lun ...
362 for(i = 0; i < sess->cam.target_nluns; i++) {
363 debug(2, "CAM path_id=%d target_id=%d target_lun=%d",
364 sess->cam.path_id, sess->cam.target_id, sess->cam.target_lun[i]);
366 sess->camdev = cam_open_btl(sess->cam.path_id, sess->cam.target_id,
367 sess->cam.target_lun[i], O_RDWR, NULL);
368 if(sess->camdev == NULL) {
369 syslog(LOG_WARNING, "%s", cam_errbuf);
370 debug(3, "%s", cam_errbuf);
371 continue;
374 cam_path_string(sess->camdev, pathstr, sizeof(pathstr));
375 debug(2, "pathstr=%s", pathstr);
377 ccb = cam_getccb(sess->camdev);
378 bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr));
379 ccb->ccb_h.func_code = XPT_REL_SIMQ;
380 ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS;
381 ccb->crs.openings = sess->op->tags;
383 if(cam_send_ccb(sess->camdev, ccb) < 0)
384 syslog(LOG_WARNING, "%s", cam_errbuf);
385 else
386 if((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
387 syslog(LOG_WARNING, "XPT_REL_SIMQ CCB failed");
388 // cam_error_print(sess->camdev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
390 else
391 syslog(LOG_INFO, "%s tagged openings now %d\n", pathstr, ccb->crs.openings);
393 cam_freeccb(ccb);
394 cam_close_device(sess->camdev);
398 static trans_t
399 supervise(isess_t *sess)
401 int sig, val;
403 debug_called(3);
405 if(strcmp(sess->op->sessionType, "Discovery") == 0) {
406 sess->flags |= SESS_DISCONNECT;
407 return T9;
410 if(vflag)
411 printf("ready to go scsi\n");
413 if(setOptions(sess, SESS_FULLFEATURE) != 0)
414 return 0; // failure
416 if((sess->flags & SESS_FULLFEATURE) == 0) {
417 if(daemon(0, 1) != 0) {
418 perror("daemon");
419 exit(1);
422 openlog("iscontrol", LOG_CONS|LOG_PERROR|LOG_PID|LOG_NDELAY, LOG_KERN);
423 syslog(LOG_INFO, "running");
425 currsess = sess;
426 if(ioctl(sess->fd, ISCSISTART)) {
427 perror("ISCSISTART");
428 return -1;
430 doCAM(sess);
433 else {
434 if(ioctl(sess->fd, ISCSIRESTART)) {
435 perror("ISCSIRESTART");
436 return -1;
440 signal(SIGINT, trap);
441 signal(SIGHUP, trap);
442 signal(SIGTERM, trap);
444 sig = SIGUSR1;
445 signal(sig, trap);
446 if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
447 perror("ISCSISIGNAL");
448 return -1;
450 sess->flags |= SESS_FULLFEATURE;
452 sess->flags &= ~(SESS_REDIRECT | SESS_RECONNECT);
453 printf("iscontrol: supervise starting main loop\n");
455 | the main loop - actually do nothing
456 | all the work is done inside the kernel
458 while((sess->flags & (SESS_REDIRECT|SESS_RECONNECT|SESS_DISCONNECT)) == 0) {
459 // do something?
460 // like sending a nop_out?
461 sleep(60);
463 printf("iscontrol: supervise going down\n");
464 syslog(LOG_INFO, "sess flags=%x", sess->flags);
466 sig = 0;
467 if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
468 perror("ISCSISIGNAL");
471 if(sess->flags & SESS_DISCONNECT) {
472 val = 0;
473 if(ioctl(sess->fd, ISCSISTOP, &val)) {
474 perror("ISCSISTOP");
476 sess->flags &= ~SESS_FULLFEATURE;
477 return T9;
479 else {
480 sess->flags |= SESS_INITIALLOGIN1;
482 return T8;
485 static int
486 handledDiscoveryResp(isess_t *sess, pdu_t *pp)
488 u_char *ptr;
489 int len, n;
491 debug_called(3);
493 len = pp->ds_len;
494 ptr = pp->ds;
495 while(len > 0) {
496 if(*ptr != 0)
497 printf("%s\n", ptr);
498 n = strlen((char *)ptr) + 1;
499 len -= n;
500 ptr += n;
502 return 0;
505 static int
506 doDiscovery(isess_t *sess)
508 pdu_t spp;
509 text_req_t *tp = (text_req_t *)&spp.ipdu.bhs;
511 debug_called(3);
513 bzero(&spp, sizeof(pdu_t));
514 tp->cmd = ISCSI_TEXT_CMD /*| 0x40 */; // because of a bug in openiscsi-target
515 tp->F = 1;
516 tp->ttt = 0xffffffff;
517 addText(&spp, "SendTargets=All");
518 return sendPDU(sess, &spp, handledDiscoveryResp);
521 static trans_t
522 doLogin(isess_t *sess)
524 isc_opt_t *op = sess->op;
525 int status, count;
527 debug_called(3);
529 if(op->chapSecret == NULL && op->tgtChapSecret == NULL)
531 | don't need any security negotiation
532 | or in other words: we don't have any secrets to exchange
534 sess->csg = LON_PHASE;
535 else
536 sess->csg = SN_PHASE;
538 if(sess->tsih) {
539 sess->tsih = 0; // XXX: no 'reconnect' yet
540 sess->flags &= ~SESS_NEGODONE; // XXX: KLUDGE
542 count = 10; // should be more than enough
543 do {
544 debug(3, "count=%d csg=%d", count, sess->csg);
545 status = loginPhase(sess);
546 if(count-- == 0)
547 // just in case we get into a loop
548 status = -1;
549 } while(status == 0 && (sess->csg != FF_PHASE));
551 sess->flags &= ~SESS_INITIALLOGIN;
552 debug(3, "status=%d", status);
554 switch(status) {
555 case 0: // all is ok ...
556 sess->flags |= SESS_LOGGEDIN;
557 if(strcmp(sess->op->sessionType, "Discovery") == 0)
558 doDiscovery(sess);
559 return T5;
561 case 1: // redirect - temporary/permanent
563 | start from scratch?
565 sess->flags &= ~SESS_NEGODONE;
566 sess->flags |= (SESS_REDIRECT | SESS_INITIALLOGIN1);
567 syslog(LOG_DEBUG, "target sent REDIRECT");
568 return T7;
570 case 2: // initiator terminal error
571 return 0;
572 case 3: // target terminal error -- could retry ...
573 sleep(5);
574 return T7; // lets try
575 default:
576 return 0;
580 static int
581 handleLogoutResp(isess_t *sess, pdu_t *pp)
583 if(sess->flags & SESS_DISCONNECT)
584 return 0;
585 return T13;
588 static trans_t
589 startLogout(isess_t *sess)
591 pdu_t spp;
592 logout_req_t *p = (logout_req_t *)&spp.ipdu.bhs;
594 bzero(&spp, sizeof(pdu_t));
595 p->cmd = ISCSI_LOGOUT_CMD| 0x40;
596 p->reason = BIT(7) | 0;
597 p->CID = htons(1);
599 return sendPDU(sess, &spp, handleLogoutResp);
602 static trans_t
603 inLogout(isess_t *sess)
605 if(sess->flags & SESS_RECONNECT)
606 return T18;
607 return 0;
610 typedef enum {
611 S1=1, S2, S3, S4, S5, S6, S7, S8
612 } state_t;
614 #if 0
615 S1: FREE
616 S2: XPT_WAIT
617 S4: IN_LOGIN
618 S5: LOGGED_IN
619 S6: IN_LOGOUT
620 S7: LOGOUT_REQUESTED
621 S8: CLEANUP_WAIT
623 -------<-------------+
624 +--------->/ S1 \<----+ |
625 T13| +->\ /<-+ \ |
626 | / ---+--- \ \ |
627 | / | T2 \ | |
628 | T8 | |T1 | | |
629 | | | / |T7 |
630 | | | / | |
631 | | | / | |
632 | | V / / |
633 | | ------- / / |
634 | | / S2 \ / |
635 | | \ / / |
636 | | ---+--- / |
637 | | |T4 / |
638 | | V / | T18
639 | | ------- / |
640 | | / S4 \ |
641 | | \ / |
642 | | ---+--- | T15
643 | | |T5 +--------+---------+
644 | | | /T16+-----+------+ |
645 | | | / -+-----+--+ | |
646 | | | / / S7 \ |T12| |
647 | | | / +->\ /<-+ V V
648 | | | / / -+----- -------
649 | | | / /T11 |T10 / S8 \
650 | | V / / V +----+ \ /
651 | | ---+-+- ----+-- | -------
652 | | / S5 \T9 / S6 \<+ ^
653 | +-----\ /--->\ / T14 |
654 | ------- --+----+------+T17
655 +---------------------------+
656 #endif
659 fsm(isc_opt_t *op)
661 state_t state;
662 isess_t *sess;
664 if((sess = calloc(1, sizeof(isess_t))) == NULL) {
665 // boy, is this a bad start ...
666 fprintf(stderr, "no memory!\n");
667 return -1;
670 state = S1;
671 sess->op = op;
672 sess->fd = -1;
673 sess->soc = -1;
674 sess->target.address = strdup(op->targetAddress);
675 sess->target.port = op->port;
676 sess->target.pgt = op->targetPortalGroupTag;
678 sess->flags = SESS_INITIALLOGIN | SESS_INITIALLOGIN1;
680 do {
681 switch(state) {
682 case S1:
683 switch(tcpConnect(sess)) {
684 case T1: state = S2; break;
685 default: state = S8; break;
687 break;
689 case S2:
690 switch(startSession(sess)) {
691 case T2: state = S1; break;
692 case T4: state = S4; break;
693 default: state = S8; break;
695 break;
697 case S4:
698 switch(doLogin(sess)) {
699 case T7: state = S1; break;
700 case T5: state = S5; break;
701 default: state = S8; break;
703 break;
705 case S5:
706 switch(supervise(sess)) {
707 case T8: state = S1; break;
708 case T9: state = S6; break;
709 case T11: state = S7; break;
710 case T15: state = S8; break;
711 default: state = S8; break;
713 break;
715 case S6:
716 switch(startLogout(sess)) {
717 case T13: state = S1; break;
718 case T14: state = S6; break;
719 case T16: state = S8; break;
720 default: state = S8; break;
722 break;
724 case S7:
725 switch(inLogout(sess)) {
726 case T18: state = S1; break;
727 case T10: state = S6; break;
728 case T12: state = S7; break;
729 case T16: state = S8; break;
730 default: state = S8; break;
732 break;
734 case S8:
735 // maybe do some clean up?
736 syslog(LOG_INFO, "terminated");
737 return 0;
738 default:
739 syslog(LOG_INFO, "unknown state %d", state);
740 return 0;
742 } while(1);