Merge branch 'vendor/BIND' into bind_vendor2
[dragonfly.git] / sbin / iscontrol / fsm.c
blob711704fa1009af0778433a8cc7e424de041b5c70
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/cdefs.h>
34 #include <sys/param.h>
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <sys/sysctl.h>
39 #include <netinet/in.h>
40 #include <netinet/tcp.h>
41 #include <arpa/inet.h>
42 #if __FreeBSD_version < 500000
43 #include <sys/time.h>
44 #endif
45 #include <sys/ioctl.h>
46 #include <netdb.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <time.h>
54 #include <syslog.h>
55 #include <stdarg.h>
56 #include <camlib.h>
58 #include "iscsi.h"
59 #include "iscontrol.h"
61 typedef enum {
62 T1 = 1,
63 T2, /*T3,*/ T4, T5, /*T6,*/ T7, T8, T9,
64 T10, T11, T12, T13, T14, T15, T16, T18
65 } trans_t;
68 | now supports IPV6
69 | thanks to:
70 | Hajimu UMEMOTO @ Internet Mutual Aid Society Yokohama, Japan
71 | ume@mahoroba.org ume@{,jp.}FreeBSD.org
72 | http://www.imasy.org/~ume/
74 static trans_t
75 tcpConnect(isess_t *sess)
77 isc_opt_t *op = sess->op;
78 int val, sv_errno, soc;
79 struct addrinfo *res, *res0, hints;
80 char pbuf[10];
82 debug_called(3);
83 if(sess->flags & (SESS_RECONNECT|SESS_REDIRECT)) {
84 syslog(LOG_INFO, "%s", (sess->flags & SESS_RECONNECT)
85 ? "Reconnect": "Redirected");
87 debug(1, "%s", (sess->flags & SESS_RECONNECT) ? "Reconnect": "Redirected");
88 shutdown(sess->soc, SHUT_RDWR);
89 //close(sess->soc);
90 sess->soc = -1;
92 sess->flags &= ~SESS_CONNECTED;
93 if(sess->flags & SESS_REDIRECT) {
94 sess->redirect_cnt++;
95 sess->flags |= SESS_RECONNECT;
96 } else
97 sleep(2); // XXX: actually should be ?
98 #ifdef notyet
100 time_t sec;
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;
110 else {
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;
121 #endif
122 sess->reconnect_cnt++;
125 snprintf(pbuf, sizeof(pbuf), "%d", op->port);
126 memset(&hints, 0, sizeof(hints));
127 hints.ai_family = PF_UNSPEC;
128 hints.ai_socktype = SOCK_STREAM;
129 debug(1, "targetAddress=%s port=%d", op->targetAddress, op->port);
130 if((val = getaddrinfo(op->targetAddress, pbuf, &hints, &res0)) != 0) {
131 fprintf(stderr, "getaddrinfo(%s): %s\n", op->targetAddress, gai_strerror(val));
132 return 0;
134 sess->flags &= ~SESS_CONNECTED;
135 sv_errno = 0;
136 soc = -1;
137 for(res = res0; res; res = res->ai_next) {
138 soc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
139 if (soc == -1)
140 continue;
142 // from Patrick.Guelat@imp.ch:
143 // iscontrol can be called without waiting for the socket entry to time out
144 val = 1;
145 if(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &val, (socklen_t)sizeof(val)) < 0) {
146 fprintf(stderr, "Cannot set socket SO_REUSEADDR %d: %s\n\n",
147 errno, strerror(errno));
149 if(connect(soc, res->ai_addr, res->ai_addrlen) == 0)
150 break;
152 sv_errno = errno;
153 close(soc);
154 soc = -1;
156 freeaddrinfo(res0);
158 if(soc != -1) {
159 sess->soc = soc;
161 /* Default to TCP_NODELAY to improve transfers */
162 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
163 fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
164 errno, strerror(errno));
166 #if 0
167 struct timeval timeout;
169 val = 1;
170 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0)
171 fprintf(stderr, "Cannot set socket KEEPALIVE option err=%d %s\n",
172 errno, strerror(errno));
174 if(setsockopt(sess->soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
175 fprintf(stderr, "Cannot set socket NO delay option err=%d %s\n",
176 errno, strerror(errno));
178 timeout.tv_sec = 10;
179 timeout.tv_usec = 0;
180 if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
181 || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)) {
182 fprintf(stderr, "Cannot set socket timeout to %ld err=%d %s\n",
183 timeout.tv_sec, errno, strerror(errno));
185 #endif
186 #ifdef CURIOUS
188 int len = sizeof(val);
189 if(getsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, &len) == 0)
190 fprintf(stderr, "was: SO_SNDBUF=%dK\n", val/1024);
192 #endif
193 if(sess->op->sockbufsize) {
194 val = sess->op->sockbufsize * 1024;
195 if((setsockopt(sess->soc, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0)
196 || (setsockopt(sess->soc, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) < 0)) {
197 fprintf(stderr, "Cannot set socket sndbuf & rcvbuf to %d err=%d %s\n",
198 val, errno, strerror(errno));
199 return 0;
202 sess->flags |= SESS_CONNECTED;
203 return T1;
206 fprintf(stderr, "errno=%d\n", sv_errno);
207 perror("connect");
208 switch(sv_errno) {
209 case ECONNREFUSED:
210 case ENETUNREACH:
211 case ETIMEDOUT:
212 if((sess->flags & SESS_REDIRECT) == 0) {
213 if(strcmp(op->targetAddress, sess->target.address) != 0) {
214 syslog(LOG_INFO, "reconnecting to original target address");
215 free(op->targetAddress);
216 op->targetAddress = sess->target.address;
217 op->port = sess->target.port;
218 op->targetPortalGroupTag = sess->target.pgt;
219 return T1;
222 sleep(5); // for now ...
223 return T1;
224 default:
225 return 0; // terminal error
230 setOptions(isess_t *sess, int flag)
232 isc_opt_t oop;
233 char *sep;
235 debug_called(3);
237 bzero(&oop, sizeof(isc_opt_t));
239 if((flag & SESS_FULLFEATURE) == 0) {
240 oop.initiatorName = sess->op->initiatorName;
241 oop.targetAddress = sess->op->targetAddress;
242 if(sess->op->targetName != 0)
243 oop.targetName = sess->op->targetName;
245 oop.maxRecvDataSegmentLength = sess->op->maxRecvDataSegmentLength;
246 oop.maxXmitDataSegmentLength = sess->op->maxXmitDataSegmentLength; // XXX:
247 oop.maxBurstLength = sess->op->maxBurstLength;
248 oop.maxluns = sess->op->maxluns;
250 else {
252 | turn on digestion only after login
254 if(sess->op->headerDigest != NULL) {
255 sep = strchr(sess->op->headerDigest, ',');
256 if(sep == NULL)
257 oop.headerDigest = sess->op->headerDigest;
258 debug(1, "oop.headerDigest=%s", oop.headerDigest);
260 if(sess->op->dataDigest != NULL) {
261 sep = strchr(sess->op->dataDigest, ',');
262 if(sep == NULL)
263 oop.dataDigest = sess->op->dataDigest;
264 debug(1, "oop.dataDigest=%s", oop.dataDigest);
268 if(ioctl(sess->fd, ISCSISETOPT, &oop)) {
269 perror("ISCSISETOPT");
270 return -1;
272 return 0;
275 static trans_t
276 startSession(isess_t *sess)
279 int n, fd, nfd;
280 char *dev;
282 debug_called(3);
284 if((sess->flags & SESS_CONNECTED) == 0) {
285 return T2;
287 if(sess->fd == -1) {
288 fd = open(iscsidev, O_RDWR);
289 if(fd < 0) {
290 perror(iscsidev);
291 return 0;
294 // XXX: this has to go
295 size_t n;
296 n = sizeof(sess->isid);
297 if(sysctlbyname("net.iscsi.isid", (void *)sess->isid, (size_t *)&n, 0, 0) != 0)
298 perror("sysctlbyname");
300 if(ioctl(fd, ISCSISETSES, &n)) {
301 perror("ISCSISETSES");
302 return 0;
304 sleep(1); /* XXX temporary */
305 asprintf(&dev, "%s%d", iscsidev, n);
306 nfd = open(dev, O_RDWR);
307 if(nfd < 0) {
308 perror(dev);
309 free(dev);
310 return 0;
312 free(dev);
313 close(fd);
314 sess->fd = nfd;
316 if(setOptions(sess, 0) != 0)
317 return -1;
320 if(ioctl(sess->fd, ISCSISETSOC, &sess->soc)) {
321 perror("ISCSISETSOC");
322 return 0;
325 return T4;
328 isess_t *currsess;
330 static void
331 trap(int sig)
333 syslog(LOG_NOTICE, "trapped signal %d", sig);
334 fprintf(stderr, "trapped signal %d\n", sig);
336 switch(sig) {
337 case SIGHUP:
338 currsess->flags |= SESS_DISCONNECT;
339 break;
341 case SIGUSR1:
342 currsess->flags |= SESS_RECONNECT;
343 break;
345 case SIGINT:
346 case SIGTERM:
347 default:
348 return; // ignore
352 static void
353 doCAM(isess_t *sess)
355 char pathstr[1024];
356 union ccb *ccb;
357 int i;
359 if(ioctl(sess->fd, ISCSIGETCAM, &sess->cam) != 0) {
360 syslog(LOG_WARNING, "ISCSIGETCAM failed: %d", errno);
361 return;
363 debug(2, "nluns=%d", sess->cam.target_nluns);
365 | for now will do this for each lun ...
367 for(i = 0; i < sess->cam.target_nluns; i++) {
368 debug(2, "CAM path_id=%d target_id=%d target_lun=%d",
369 sess->cam.path_id, sess->cam.target_id, sess->cam.target_lun[i]);
371 sess->camdev = cam_open_btl(sess->cam.path_id, sess->cam.target_id,
372 sess->cam.target_lun[i], O_RDWR, NULL);
373 if(sess->camdev == NULL) {
374 syslog(LOG_WARNING, "%s", cam_errbuf);
375 debug(3, "%s", cam_errbuf);
376 continue;
379 cam_path_string(sess->camdev, pathstr, sizeof(pathstr));
380 debug(2, "pathstr=%s", pathstr);
382 ccb = cam_getccb(sess->camdev);
383 bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_relsim) - sizeof(struct ccb_hdr));
384 ccb->ccb_h.func_code = XPT_REL_SIMQ;
385 ccb->crs.release_flags = RELSIM_ADJUST_OPENINGS;
386 ccb->crs.openings = sess->op->tags;
388 if(cam_send_ccb(sess->camdev, ccb) < 0)
389 syslog(LOG_WARNING, "%s", cam_errbuf);
390 else
391 if((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
392 syslog(LOG_WARNING, "XPT_REL_SIMQ CCB failed");
393 // cam_error_print(sess->camdev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
395 else
396 syslog(LOG_INFO, "%s tagged openings now %d\n", pathstr, ccb->crs.openings);
398 cam_freeccb(ccb);
399 cam_close_device(sess->camdev);
403 static trans_t
404 supervise(isess_t *sess)
406 int sig, val;
408 debug_called(3);
410 if(strcmp(sess->op->sessionType, "Discovery") == 0) {
411 sess->flags |= SESS_DISCONNECT;
412 return T9;
415 if(vflag)
416 printf("ready to go scsi\n");
418 if(setOptions(sess, SESS_FULLFEATURE) != 0)
419 return 0; // failure
421 if((sess->flags & SESS_FULLFEATURE) == 0) {
422 if(daemon(0, 1) != 0) {
423 perror("daemon");
424 exit(1);
427 openlog("iscontrol", LOG_CONS|LOG_PERROR|LOG_PID|LOG_NDELAY, LOG_KERN);
428 syslog(LOG_INFO, "running");
430 currsess = sess;
431 if(ioctl(sess->fd, ISCSISTART)) {
432 perror("ISCSISTART");
433 return -1;
435 doCAM(sess);
438 else {
439 if(ioctl(sess->fd, ISCSIRESTART)) {
440 perror("ISCSIRESTART");
441 return -1;
445 signal(SIGINT, trap);
446 signal(SIGHUP, trap);
447 signal(SIGTERM, trap);
449 sig = SIGUSR1;
450 signal(sig, trap);
451 if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
452 perror("ISCSISIGNAL");
453 return -1;
455 sess->flags |= SESS_FULLFEATURE;
457 sess->flags &= ~(SESS_REDIRECT | SESS_RECONNECT);
458 printf("iscontrol: supervise starting main loop\n");
460 | the main loop - actually do nothing
461 | all the work is done inside the kernel
463 while((sess->flags & (SESS_REDIRECT|SESS_RECONNECT|SESS_DISCONNECT)) == 0) {
464 // do something?
465 // like sending a nop_out?
466 sleep(60);
468 printf("iscontrol: supervise going down\n");
469 syslog(LOG_INFO, "sess flags=%x", sess->flags);
471 sig = 0;
472 if(ioctl(sess->fd, ISCSISIGNAL, &sig)) {
473 perror("ISCSISIGNAL");
476 if(sess->flags & SESS_DISCONNECT) {
477 val = 0;
478 if(ioctl(sess->fd, ISCSISTOP, &val)) {
479 perror("ISCSISTOP");
481 sess->flags &= ~SESS_FULLFEATURE;
482 return T9;
484 else {
485 sess->flags |= SESS_INITIALLOGIN1;
487 return T8;
490 static int
491 handledDiscoveryResp(isess_t *sess, pdu_t *pp)
493 u_char *ptr;
494 int len, n;
496 debug_called(3);
498 len = pp->ds_len;
499 ptr = pp->ds;
500 while(len > 0) {
501 if(*ptr != 0)
502 printf("%s\n", ptr);
503 n = strlen((char *)ptr) + 1;
504 len -= n;
505 ptr += n;
507 return 0;
510 static int
511 doDiscovery(isess_t *sess)
513 pdu_t spp;
514 text_req_t *tp = (text_req_t *)&spp.ipdu.bhs;
516 debug_called(3);
518 bzero(&spp, sizeof(pdu_t));
519 tp->cmd = ISCSI_TEXT_CMD /*| 0x40 */; // because of a bug in openiscsi-target
520 tp->F = 1;
521 tp->ttt = 0xffffffff;
522 addText(&spp, "SendTargets=All");
523 return sendPDU(sess, &spp, handledDiscoveryResp);
526 static trans_t
527 doLogin(isess_t *sess)
529 isc_opt_t *op = sess->op;
530 int status, count;
532 debug_called(3);
534 if(op->chapSecret == NULL && op->tgtChapSecret == NULL)
536 | don't need any security negotiation
537 | or in other words: we don't have any secrets to exchange
539 sess->csg = LON_PHASE;
540 else
541 sess->csg = SN_PHASE;
543 if(sess->tsih) {
544 sess->tsih = 0; // XXX: no 'reconnect' yet
545 sess->flags &= ~SESS_NEGODONE; // XXX: KLUDGE
547 count = 10; // should be more than enough
548 do {
549 debug(3, "count=%d csg=%d", count, sess->csg);
550 status = loginPhase(sess);
551 if(count-- == 0)
552 // just in case we get into a loop
553 status = -1;
554 } while(status == 0 && (sess->csg != FF_PHASE));
556 sess->flags &= ~SESS_INITIALLOGIN;
557 debug(3, "status=%d", status);
559 switch(status) {
560 case 0: // all is ok ...
561 sess->flags |= SESS_LOGGEDIN;
562 if(strcmp(sess->op->sessionType, "Discovery") == 0)
563 doDiscovery(sess);
564 return T5;
566 case 1: // redirect - temporary/permanent
568 | start from scratch?
570 sess->flags &= ~SESS_NEGODONE;
571 sess->flags |= (SESS_REDIRECT | SESS_INITIALLOGIN1);
572 syslog(LOG_DEBUG, "target sent REDIRECT");
573 return T7;
575 case 2: // initiator terminal error
576 return 0;
577 case 3: // target terminal error -- could retry ...
578 sleep(5);
579 return T7; // lets try
580 default:
581 return 0;
585 static int
586 handleLogoutResp(isess_t *sess, pdu_t *pp)
588 if(sess->flags & SESS_DISCONNECT)
589 return 0;
590 return T13;
593 static trans_t
594 startLogout(isess_t *sess)
596 pdu_t spp;
597 logout_req_t *p = (logout_req_t *)&spp.ipdu.bhs;
599 bzero(&spp, sizeof(pdu_t));
600 p->cmd = ISCSI_LOGOUT_CMD| 0x40;
601 p->reason = BIT(7) | 0;
602 p->CID = htons(1);
604 return sendPDU(sess, &spp, handleLogoutResp);
607 static trans_t
608 inLogout(isess_t *sess)
610 if(sess->flags & SESS_RECONNECT)
611 return T18;
612 return 0;
615 typedef enum {
616 S1=1, S2, S3, S4, S5, S6, S7, S8
617 } state_t;
619 #if 0
620 S1: FREE
621 S2: XPT_WAIT
622 S4: IN_LOGIN
623 S5: LOGGED_IN
624 S6: IN_LOGOUT
625 S7: LOGOUT_REQUESTED
626 S8: CLEANUP_WAIT
628 -------<-------------+
629 +--------->/ S1 \<----+ |
630 T13| +->\ /<-+ \ |
631 | / ---+--- \ \ |
632 | / | T2 \ | |
633 | T8 | |T1 | | |
634 | | | / |T7 |
635 | | | / | |
636 | | | / | |
637 | | V / / |
638 | | ------- / / |
639 | | / S2 \ / |
640 | | \ / / |
641 | | ---+--- / |
642 | | |T4 / |
643 | | V / | T18
644 | | ------- / |
645 | | / S4 \ |
646 | | \ / |
647 | | ---+--- | T15
648 | | |T5 +--------+---------+
649 | | | /T16+-----+------+ |
650 | | | / -+-----+--+ | |
651 | | | / / S7 \ |T12| |
652 | | | / +->\ /<-+ V V
653 | | | / / -+----- -------
654 | | | / /T11 |T10 / S8 \
655 | | V / / V +----+ \ /
656 | | ---+-+- ----+-- | -------
657 | | / S5 \T9 / S6 \<+ ^
658 | +-----\ /--->\ / T14 |
659 | ------- --+----+------+T17
660 +---------------------------+
661 #endif
664 fsm(isc_opt_t *op)
666 state_t state;
667 isess_t *sess;
669 if((sess = calloc(1, sizeof(isess_t))) == NULL) {
670 // boy, is this a bad start ...
671 fprintf(stderr, "no memory!\n");
672 return -1;
675 state = S1;
676 sess->op = op;
677 sess->fd = -1;
678 sess->soc = -1;
679 sess->target.address = strdup(op->targetAddress);
680 sess->target.port = op->port;
681 sess->target.pgt = op->targetPortalGroupTag;
683 sess->flags = SESS_INITIALLOGIN | SESS_INITIALLOGIN1;
685 do {
686 switch(state) {
687 case S1:
688 switch(tcpConnect(sess)) {
689 case T1: state = S2; break;
690 default: state = S8; break;
692 break;
694 case S2:
695 switch(startSession(sess)) {
696 case T2: state = S1; break;
697 case T4: state = S4; break;
698 default: state = S8; break;
700 break;
702 case S4:
703 switch(doLogin(sess)) {
704 case T7: state = S1; break;
705 case T5: state = S5; break;
706 default: state = S8; break;
708 break;
710 case S5:
711 switch(supervise(sess)) {
712 case T8: state = S1; break;
713 case T9: state = S6; break;
714 case T11: state = S7; break;
715 case T15: state = S8; break;
716 default: state = S8; break;
718 break;
720 case S6:
721 switch(startLogout(sess)) {
722 case T13: state = S1; break;
723 case T14: state = S6; break;
724 case T16: state = S8; break;
725 default: state = S8; break;
727 break;
729 case S7:
730 switch(inLogout(sess)) {
731 case T18: state = S1; break;
732 case T10: state = S6; break;
733 case T12: state = S7; break;
734 case T16: state = S8; break;
735 default: state = S8; break;
737 break;
739 case S8:
740 // maybe do some clean up?
741 syslog(LOG_INFO, "terminated");
742 return 0;
743 default:
744 syslog(LOG_INFO, "unknown state %d", state);
745 return 0;
747 } while(1);