1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * ident "@(#)JISTAgent.java 1.25 08/12/04 SMI" 27 */ 28 29 package com.sun.jist; 30 import com.sun.jist.JISTData; 31 import com.sun.jist.JISTLogic; 32 import com.sun.jist.iscsi.iSCSIData; 33 import java.io.BufferedWriter; 34 import java.io.IOException; 35 import java.io.OutputStreamWriter; 36 import java.io.Writer; 37 import java.net.InetAddress; 38 import java.net.ServerSocket; 39 import java.net.Socket; 40 import java.net.SocketException; 41 import java.net.SocketTimeoutException; 42 import java.net.UnknownHostException; 43 44 /** 45 * JIST Proxy Agent used to intercept communications for Queries, 46 * Additions, Changes, and/or Deletions. 47 * <p> 48 * Verifies Compliance to IETF iSCSI and allows for SCSI fault 49 * injection. 50 * <p> 51 * @author Joel.Buckley (at) Sun.COM 52 * @since 5.0 53 */ 54 public class JISTAgent extends JISTLogic { 55 56 /** Declare & Initialize "public final static" Constants. */ 57 58 /** 59 * Spin cycle in Milliseconds. 60 * <p> 61 * This is used for Read & Write Threads to avoid hangs. 62 */ 63 private final static long SPIN = 50L; 64 65 /** Data IN "READ" Direction. */ 66 private final static byte DATA_IN = 0; 67 68 /** Data OUT "WRITE" Direction. */ 69 private final static byte DATA_OUT = 1; 70 71 /** 72 * JIST iSCSI Proxy Target Server Socket IP Address. 73 * <p> 74 * This is set by Java Environment Property "proxyIP=localhost". By default, 75 * this is set to localhost or 127.0.0.1. 76 */ 77 public final static String proxyIP; 78 79 /** Proxy Port Mutex. */ 80 private final static Object proxyPortMutex = new Object(); 81 82 /** Declare "static" Class Variables. */ 83 84 /** 85 * JIST iSCSI Proxy Management Server Socket Port. 86 * <p> 87 * This is set by Java Environment Property "mgntPort=ffffffff". By 88 * default this is set to -1 and must be specified. 89 */ 90 public static volatile int mgntPort; 91 92 /** 93 * JISTAgent Thread number, per JVM Counter. 94 */ 95 private static volatile int threadID; 96 97 /** 98 * JIST iSCSI Proxy Target Server Socket Port. 99 * <p> 100 * This is set by Java Environment Property "proxyPort=ffffffff". By 101 * default this is set to -1 and must be specified. 102 */ 103 public static volatile int[] proxyPort; 104 105 /** 106 * Actual iSCSI Target Server Socket Port. 107 * <p> 108 * This is set by Java Environment Property "targetPort=cbc". By 109 * default, this is set to 3260 or 0x0cbc. 110 */ 111 public static volatile int[] targetPort; 112 113 /** 114 * Actual iSCSI Target Server Socket IP Address. 115 * <p> 116 * This is set by Java Environment Property "targetIP=localhost". By 117 * default, this is set to localhost or 127.0.0.1. 118 */ 119 public static volatile String[] targetIP; 120 121 /** Declare non-"static" Instance Variables. */ 122 123 /** 124 * JISTAgent Thread number, instance specific. 125 */ 126 private volatile String myID; 127 128 /** Declare and Initialize non-"static" Instance Constants. */ 129 130 /** 131 * JISTAgent ServerSocket Thread. 132 */ 133 private final boolean serverSocket; 134 135 /** 136 * JISTAgent Read or Write Thread. 137 */ 138 private final boolean readAgent; 139 140 /** 141 * JIST iSCSI Proxy Target Server Index. 142 * <p> 143 * This value is used associate a ProxyPort with a TargetIP/TargetPort 144 * combination. This is necessary for iSCSI Target Port Redirection. 145 */ 146 private final int proxyIndex; 147 148 /** 149 * JISTAgent Master Thread. 150 */ 151 private final JISTAgent master; 152 153 /** Initialize "static" Class Variables. */ 154 static { 155 /** Booleans. */ 156 157 /** I/O Paths. */ 158 159 /** Test Settings. */ 160 mgntPort = getIntProperty("mgntPort", -1); 161 proxyPort = new int[1]; 162 proxyPort[0] = getIntProperty("proxyPort", -1); 163 targetPort = new int[1]; 164 targetPort[0] = getIntProperty("targetPort", 3260); 165 String ip; 166 try { 167 ip = InetAddress.getLocalHost().getHostAddress(); 168 ip = InetAddress.getByName(getStringProperty("proxyIP", ip)) 169 .getHostAddress(); 170 } catch (UnknownHostException e) { 171 ip = "127.0.0.1"; 172 } 173 proxyIP = ip; 174 targetIP = new String[1]; 175 try { 176 ip = InetAddress.getLocalHost().getHostAddress(); 177 ip = InetAddress.getByName(getStringProperty("targetIP", ip)) 178 .getHostAddress(); 179 } catch (UnknownHostException e) { 180 ip = "127.0.0.1"; 181 } 182 targetIP[0] = ip; 183 184 /** Internal Variables - long. */ 185 186 /** Internal Variables - int. */ 187 threadID = 0; 188 189 /** Internal Variables - short. */ 190 191 /** Internal Variables - byte. */ 192 193 /** Internal Variables - Arrays. */ 194 195 /** Internal Variables - String. */ 196 197 /** Internal Variables - misc. */ 198 } 199 200 /** Initialize non-"static" Instance Variables. */ 201 { 202 /** Booleans. */ 203 204 /** I/O Paths. */ 205 206 /** Test Settings. */ 207 208 /** Internal Variables - long. */ 209 210 /** Internal Variables - int. */ 211 212 /** Internal Variables - short. */ 213 214 /** Internal Variables - byte. */ 215 216 /** Internal Variables - Arrays. */ 217 218 /** Internal Variables - String. */ 219 myID = "JISTAgent" + (++threadID); 220 221 /** Internal Variables - misc. */ 222 } 223 224 /** 225 * First constructor for JIST Agent ServerSocket use. 226 * <p> 227 * Specifing a negative value for proxyIndex starts the JIST Proxy Management 228 * Server. 229 * <p> 230 * Specifing a positive value for proxyIndex starts a JIST Proxy iSCSI Target 231 * Server. 232 * <p> 233 * @param proxyIndex JIST Proxy iSCSI Target Server Index. 234 */ 235 public JISTAgent(int proxyIndex) { 236 super(); 237 master = this; 238 tester = this; 239 serverSocket = true; 240 readAgent = true; 241 this.dataAgent = (proxyIndex >= 0); 242 this.proxyIndex = proxyIndex; 243 } 244 245 /** 246 * Second constructor to establish a new JIST Agent socket. 247 * <p> 248 * @param socket JISTAgent external Socket socket. 249 * @param proxyIndex JIST Proxy iSCSI Target Server Index. 250 */ 251 private JISTAgent(Socket socket, int proxyIndex) { 252 super(); 253 master = this; 254 tester = this; 255 serverSocket = false; 256 readAgent = true; 257 this.socket = socket; 258 this.dataAgent = (proxyIndex >= 0); 259 this.targetAgent = true; 260 this.proxyIndex = proxyIndex; 261 262 if (dataAgent) { 263 if (mgntPort == -1) { 264 // Create Initiator Read Thread 265 this.peer = new JISTAgent(this, proxyIndex, dataAgent, 266 !targetAgent); 267 } else { 268 // Create Management Write Thread 269 this.peer = new JISTAgent(this, proxyIndex, !dataAgent, 270 targetAgent); 271 } 272 Thread t = new Thread(peer); 273 t.setPriority(Thread.NORM_PRIORITY + 1); 274 t.start(); 275 } 276 } 277 278 /** 279 * Third constructor to link JIST Initiator, Delta, & Target Agents. 280 * <p> 281 * In this constructor, the peer passes a reference to itself 282 * such that bidirectional communications can be establish 283 * between the JISTAgent instances. 284 * <p> 285 * @param peer JISTAgent Peer Thread asking for the object. 286 * @param proxyIndex JIST Proxy iSCSI Target Server Index. 287 * @param targetAgent JISTAgent Target or Initiator Thread flag. 288 */ 289 private JISTAgent(JISTAgent peer, int proxyIndex, boolean dataAgent, 290 boolean targetAgent) { 291 super(); 292 master = this; 293 tester = this; 294 serverSocket = false; 295 this.peer = peer; 296 this.proxyIndex = proxyIndex; 297 this.dataAgent = dataAgent; 298 this.targetAgent = targetAgent; 299 dupBufferedWriter(peer); 300 301 /* 302 * Data Agents default to readAgent as primary thread. 303 * Management Agents default to write agent as primary thread. 304 */ 305 this.readAgent = dataAgent; 306 307 if (mgntPort != -1 && !dataAgent) { 308 // Create Initiator Read Thread 309 this.peer2 = 310 new JISTAgent(this, proxyIndex, !dataAgent, !targetAgent); 311 Thread t = new Thread(peer2); 312 t.setPriority(Thread.NORM_PRIORITY + 1); 313 t.start(); 314 } 315 } 316 317 /** 318 * Forth constructor to Start Read & Write Threads. 319 * <p> 320 * In this constructor, the peer passes a reference to itself 321 * such that bidirectional communications can be establish 322 * between the JISTAgent instances. 323 * <p> 324 * @param master JISTAgent Master Thread asking for the object. 325 * @param dataAgent JISTAgent Data or Management Thread flag. 326 * @param targetAgent JISTAgent Target or Initiator Thread flag. 327 * @param readAgent JISTAgent Read or Write Thread flag. 328 */ 329 private JISTAgent(JISTAgent master, boolean dataAgent, boolean targetAgent, 330 boolean readAgent) { 331 super(); 332 serverSocket = false; 333 this.master = master; 334 this.tester = master; 335 this.proxyIndex = master.proxyIndex; 336 this.dataAgent = dataAgent; 337 this.targetAgent = targetAgent; 338 this.readAgent = readAgent; 339 this.peer = master.peer; 340 dupBufferedWriter(tester); 341 } 342 343 /** 344 * Main entry point. 345 */ 346 public static void main(String[] args) { 347 Thread t = new Thread(new JISTAgent(0)); // Proxy 348 t.setPriority(Thread.NORM_PRIORITY + 1); 349 t.start(); 350 new Thread(new JISTAgent(-1)).start(); // Management 351 /* The above threads will prevent the object from exiting... */ 352 } 353 354 /** 355 * Main execution engines for listening ServerSocket, individual 356 * socket reader, and individual socket writers. 357 */ 358 public void run() { 359 /** Verify new Thread full name. */ 360 if (reportLevel > 8) { 361 myID = new StringBuilder(myID) 362 .append(dataAgent ? ":\"Data " : ":\"Management ") 363 .append(targetAgent ? "Target " : "Initiator ") 364 .append(readAgent ? "Read " : "Write ") 365 .append(serverSocket ? "Server\"" : "Connection\"") 366 .toString(); 367 } 368 Thread.currentThread().setName(myID); 369 370 log(MILESTONE, "JISTAgent Thread Start", myID); 371 if (serverSocket) { 372 // Handle ServerSockets here. 373 listen(); 374 } else if (!dataAgent && readAgent) { 375 // Handle Management Connections. 376 interact(); 377 } else if (readAgent) { 378 // Start Write Connection Thread. 379 Thread t = new Thread(new JISTAgent( 380 this, dataAgent, targetAgent, false)); 381 t.setPriority(Thread.NORM_PRIORITY + 1); 382 t.start(); 383 384 // Data Read Connection Threads. 385 if (master.openByteSocket(master, targetIP[proxyIndex], 386 targetPort[proxyIndex])) { 387 JISTData obj; 388 while ((obj = new iSCSIData()) != null && 389 master.read(obj, 3600) && peer.writeQueue(obj)); 390 } 391 master.deStress(master); 392 master.close(master); 393 } else { 394 // Data Write Connection Threads. 395 if (master.openByteSocket(master, targetIP[proxyIndex], 396 targetPort[proxyIndex])) { 397 /** Core loop for Write Threads. No Printing Here. */ 398 while (running && tester.stressing && 399 master.writeImmediate( 400 ((JISTData)master.dequeue()))); 401 } 402 master.deStress(master); 403 master.close(master); 404 } 405 log(MILESTONE, "JISTAgent Thread Complete", myID); 406 } 407 408 /** 409 * Set Stressing to false to speed data socket thread shutdown. 410 * <p> 411 * @param customer Who is asking for the service. 412 */ 413 private final void deStress(JISTAgent customer) { 414 tester.stressing = false; 415 if (null != peer2 && peer2 != customer) { 416 peer2.deStress(customer); 417 } 418 if (null != peer && peer != customer) { 419 peer.deStress(customer); 420 } 421 } 422 423 /** 424 * Listen for incoming socket connections. 425 * <p> 426 * Note: All output goes to the console. 427 */ 428 private final void listen() { 429 int localPort, remotePort; 430 String remoteIP; 431 ServerSocket ss = null; 432 Thread t = null; 433 434 if (proxyIndex < 0) { 435 log(MILESTONE, "Security Notice", getMsg(107)); 436 } 437 synchronized (proxyPortMutex) { 438 if (!dataAgent) { 439 localPort = proxyPort[0]; 440 remotePort = -1; 441 } else if (proxyIndex < 0 || proxyIndex >= proxyPort.length) { 442 localPort = -1; 443 remotePort = -1; 444 } else { 445 localPort = proxyPort[proxyIndex]; 446 remotePort = targetPort[proxyIndex]; 447 } 448 } 449 450 if (!dataAgent && mgntPort < 0) { 451 /** No Delta Agent. Nothing else to do. */ 452 return; 453 } else if (dataAgent && localPort < 0) { 454 /** No Proxy Agent. Nothing else to do. */ 455 return; 456 } else if (mgntPort == localPort) { 457 log(WARNING, 458 "JIST iSCSI Proxy Management & Target ports conflict", 459 getMsg(902)); 460 localPort = -1; 461 } else if (dataAgent && localPort == remotePort) { 462 synchronized (proxyPortMutex) { 463 remoteIP = targetIP[proxyIndex]; 464 } 465 try { 466 if (InetAddress.getByName(remoteIP) 467 .isAnyLocalAddress()) { 468 log(WARNING, 469 "JIST Proxy & Target Ports conflict.", 470 getMsg(902)); 471 localPort = -1; 472 } 473 } catch (UnknownHostException uhe) { 474 log(WARNING, "iSCSI Target IP Address undefined", 475 getMsg(902)); 476 localPort = -1; 477 } 478 } 479 480 /** Unlock thread that created this thread. */ 481 if (dataAgent && localPort < 0) { 482 synchronized (proxyPortMutex) { 483 proxyPort[proxyIndex] = localPort; 484 proxyPortMutex.notifyAll(); 485 } 486 return; 487 } 488 489 try { 490 if (dataAgent) { 491 ss = new ServerSocket(localPort); 492 localPort = ss.getLocalPort(); 493 synchronized (proxyPortMutex) { 494 proxyPort[proxyIndex] = localPort; 495 proxyPortMutex.notifyAll(); 496 } 497 } else { 498 ss = new ServerSocket(mgntPort); 499 } 500 ss.setSoTimeout(1000); // 1 second timeout. 501 while (running) { 502 try { 503 // New Target or Management Read Thread 504 t = new Thread(new JISTAgent(ss.accept(), 505 proxyIndex)); 506 t.setPriority(Thread.NORM_PRIORITY + 1); 507 t.start(); 508 } catch (SocketTimeoutException e) { 509 // keep checking running variable... 510 } 511 } 512 } catch (IOException e) { 513 log(WARNING, "JISTAgent Server Socket, Unexpected Error", e); 514 } finally { 515 try { 516 if (ss != null) { 517 ss.close(); 518 } 519 } catch (IOException e2) { 520 log(DEBUG, "Close Server Socket Exception", e2); 521 } 522 } 523 } 524 525 /** 526 * Interact with management socket connections. 527 * <p> 528 * Note:jj 529 */ 530 private final void interact() { 531 final String complete = getMsg(196) + newline; 532 final String prompt = getMsg(410); 533 final String who = new StringBuilder(myID).append(" from ") 534 .append(socket.getInetAddress().getCanonicalHostName()).toString(); 535 String command; 536 if (openCharSocket(this, "", -1)) { 537 log(MILESTONE, "JISTAgent Thread Interacting", who); 538 while (running) { 539 if (!write(prompt)) { 540 log(WARNING, "JISTAgent Thread Socket Closed", 541 who); 542 break; 543 } 544 if ((command = readLine()) == null) { 545 log(WARNING, "JISTAgent Thread Input Closed", 546 who); 547 break; 548 } 549 if (command.equals("") || command.matches("\\s*")) { 550 continue; 551 } 552 if (command.equals("exitall")) { 553 log(MILESTONE, "JISTAgent Thread Exit All", 554 who); 555 running = false; 556 close(this); 557 break; 558 } 559 if (command.equals("exit")) { 560 log(MILESTONE, "JISTAgent Thread Exit", who); 561 close(this); 562 break; 563 } 564 if (!running) { 565 log(MILESTONE, "JISTAgent Thread Not Running", 566 who); 567 close(this); 568 break; 569 } 570 if (command.startsWith("inject")) { 571 String[] args = command.split("\\s"); 572 if (args.length > 1) { 573 while (!JISTData.addMask(args[1])) { 574 write(getMsg(411)); 575 } 576 } 577 } else if (command.startsWith("com.sun.jist")) { 578 execute(command); 579 } else if (!write(new StringBuilder( 580 "Unrecognized request \"").append(command) 581 .append("\", skipping.").toString())) { 582 break; 583 } 584 if (!write(complete)) { 585 log(WARNING, "JISTAgent Thread Output Closed", 586 who); 587 break; 588 } 589 } 590 } else { 591 log(WARNING, "JISTAgent Thread Socket DOA", who); 592 } 593 } 594 595 /** 596 * Write JISTData Object to Byte Stream Output. 597 * <p> 598 * @param data JIST Data Object to be written. 599 */ 600 private final boolean writeQueue(JISTData data) { 601 if (data == null) { 602 // Read failure... initiate closing process. 603 log(DEBUG, "JISTAgent Write Data Null", 604 new StringBuilder(myID).append(" Called By ") 605 .append(Thread.currentThread().getName()).toString()); 606 return false; 607 } 608 if (data.getImmediate() == 0 && master.enqueue(data)) { 609 // Regular Command and available Queue. 610 return true; 611 } else { 612 // PDU Immediate bit set or uninitialized Queue. 613 return writeImmediate(data); 614 } 615 } 616 617 /** 618 * Internal write method behind write queue. 619 * <p> 620 * @param data JIST Data Object to be written. 621 */ 622 private final boolean writeImmediate(JISTData data) { 623 if (data == null) { 624 // Read failure... initiate closing process. 625 log(DEBUG, "JISTAgent Write Data Null", 626 new StringBuilder(myID).append(" Called By ") 627 .append(Thread.currentThread().getName()).toString()); 628 return false; 629 } 630 if (dataAgent) { 631 if (master.write(data) && 632 data.getOpcode() != 0x026) { 633 return true; 634 } else { 635 /** 636 * Initiate closing process on failed write or 637 * iSCSI Logout Request PDU. 638 */ 639 return false; 640 } 641 } else { 642 // Data Manipulation done here... 643 if (reportLevel > 8) { 644 log(DEBUG, "JISTAgent iSCSI PDU Header, before Mask", 645 data.getHeader()); 646 log(DEBUG, "JISTAgent iSCSI PDU Payload, before Mask", 647 data.getPayload()); 648 switch (data.getOpcode()) { 649 case(0x003): 650 case(0x004): 651 case(0x023): 652 case(0x024): 653 log(DEBUG, "JISTAgent iSCSI PDU " + 654 "Payload, before Mask, in ASCII", 655 new String(data.getPayload(), 0, 656 data.getPayload().length) 657 .replaceAll("\00", newline)); 658 break; 659 } 660 } 661 662 663 JISTData atad; // Data backwards :) 664 JISTData[] masked = data.mask(); 665 if (masked == null) { 666 /** Attempting to Disconnect... Aburptly. */ 667 return false; 668 } 669 int index = masked.length; 670 671 while (index > 0) { 672 atad = masked[--index]; 673 674 if (atad == null) { 675 continue; 676 } 677 678 if (reportLevel > 8) { 679 log(DEBUG, 680 "JISTAgent iSCSI PDU Header, after Mask", 681 atad.getHeader()); 682 log(DEBUG, 683 "JISTAgent iSCSI PDU Payload, after Mask", 684 atad.getPayload()); 685 switch (data.getOpcode()) { 686 case(0x003): 687 case(0x004): 688 case(0x023): 689 case(0x024): 690 log(DEBUG, "JISTAgent iSCSI PDU " + 691 "Payload, after Mask, in ASCII", 692 new String(atad.getPayload(), 0, 693 atad.getPayload().length) 694 .replaceAll("\00", newline)); 695 break; 696 } 697 } 698 699 if (atad.getDirection() == DATA_IN) { 700 if (!peer.writeQueue(atad)) { 701 return false; 702 } 703 } else { 704 if (!peer2.writeQueue(atad)) { 705 return false; 706 } 707 } 708 } 709 710 return true; 711 } 712 } 713 714 /** 715 * Common method to lookup and, if necessary, increment the number of JIST 716 * iSCSI Proxy Target Servers. 717 * <p> 718 * @param ip iSCSI Target IP Address. 719 * @param port iSCSI Target Port Number. 720 * @return proxyIndex Position where IP/Port combination was placed. 721 */ 722 public static int getProxyIndex(String ip, int port) { 723 int inx; 724 int[] proxyPortNew, targetPortNew; 725 String[] targetIPNew; 726 Thread t; 727 728 synchronized (proxyPortMutex) { 729 inx = targetIP.length; 730 731 /** Is the IP:Port combination served by a Proxy? */ 732 while (--inx >= 0) { 733 if (targetPort[inx] == port && 734 targetIP[inx].equals(ip)) { 735 return (inx); 736 } 737 } 738 739 /** Well, Proxy not found... Create one... */ 740 inx = targetIP.length; 741 742 proxyPortNew = new int[inx + 1]; 743 System.arraycopy(proxyPort, 0, proxyPortNew, 0, 744 proxyPort.length); 745 proxyPortNew[inx] = 0; /** Temp. until Thread started below. */ 746 proxyPort = proxyPortNew; 747 748 targetPortNew = new int[inx + 1]; 749 System.arraycopy(targetPort, 0, targetPortNew, 0, 750 targetPort.length); 751 targetPortNew[inx] = port; 752 targetPort = targetPortNew; 753 754 targetIPNew = new String[inx + 1]; 755 System.arraycopy(targetIP, 0, targetIPNew, 0, targetIP.length); 756 targetIPNew[inx] = ip; 757 targetIP = targetIPNew; 758 759 /** Start the JIST iSCSI Proxy Target Server. */ 760 t = new Thread(new JISTAgent(inx)); // Proxy 761 t.setPriority(Thread.NORM_PRIORITY + 1); 762 } 763 764 /** Start the Thread outside the Synchronized Block. */ 765 t.start(); 766 767 synchronized (proxyPortMutex) { 768 /** Wait for the real proxyPort to be assigned. */ 769 boolean i = false; 770 while (proxyPort[inx] == 0) { 771 try { 772 proxyPortMutex.wait(SPIN); 773 } catch (InterruptedException e) { 774 i = true; 775 } 776 } 777 if (i) { 778 Thread.currentThread().interrupt(); 779 } 780 } 781 782 return (inx); 783 } 784 785 } /* Class End */ 786