ISCSIServer.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Net;
  11. using System.Net.Sockets;
  12. using System.Text;
  13. using System.Threading;
  14. using Utilities;
  15. namespace ISCSI.Server
  16. {
  17. public delegate ushort GetNextTSIH();
  18. /// <summary>
  19. /// an iSCSI server that can serve multiple iSCSI targets
  20. /// </summary>
  21. public partial class ISCSIServer
  22. {
  23. public const int DefaultPort = 3260;
  24. private IPEndPoint m_listenerEP;
  25. private List<ISCSITarget> m_targets;
  26. private ushort m_nextTSIH = 1; // Next Target Session Identifying Handle
  27. private Socket m_listenerSocket;
  28. private bool m_listening;
  29. public ConnectionManager m_connectionManager = new ConnectionManager();
  30. public event EventHandler<LogEntry> OnLogEntry;
  31. public ISCSIServer(List<ISCSITarget> targets) : this(targets, DefaultPort)
  32. { }
  33. /// <summary>
  34. /// Server needs to be started with Start()
  35. /// </summary>
  36. /// <param name="port">The port on which the iSCSI server will listen</param>
  37. public ISCSIServer(List<ISCSITarget> targets, int port) : this(targets, new IPEndPoint(IPAddress.Any, port))
  38. {
  39. }
  40. /// <summary>
  41. /// Server needs to be started with Start()
  42. /// </summary>
  43. /// <param name="listenerEP">The endpoint on which the iSCSI server will listen</param>
  44. public ISCSIServer(List<ISCSITarget> targets, IPEndPoint listenerEP)
  45. {
  46. m_listenerEP = listenerEP;
  47. m_targets = targets;
  48. }
  49. public void Start()
  50. {
  51. if (!m_listening)
  52. {
  53. Log(Severity.Information, "Starting Server");
  54. m_listening = true;
  55. m_listenerSocket = new Socket(m_listenerEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  56. m_listenerSocket.Bind(m_listenerEP);
  57. m_listenerSocket.Listen(1000);
  58. m_listenerSocket.BeginAccept(ConnectRequestCallback, m_listenerSocket);
  59. }
  60. }
  61. // This method accepts new connections
  62. private void ConnectRequestCallback(IAsyncResult ar)
  63. {
  64. Socket listenerSocket = (Socket)ar.AsyncState;
  65. Socket clientSocket;
  66. try
  67. {
  68. clientSocket = listenerSocket.EndAccept(ar);
  69. }
  70. catch (ObjectDisposedException)
  71. {
  72. return;
  73. }
  74. catch (SocketException)
  75. {
  76. return;
  77. }
  78. Log(Severity.Information, "New connection has been accepted");
  79. ConnectionState state = new ConnectionState();
  80. state.ConnectionParameters.InitiatorEndPoint = clientSocket.RemoteEndPoint as IPEndPoint;
  81. state.ReceiveBuffer = new byte[ConnectionState.ReceiveBufferSize];
  82. // Disable the Nagle Algorithm for this tcp socket:
  83. clientSocket.NoDelay = true;
  84. state.ClientSocket = clientSocket;
  85. Thread senderThread = new Thread(delegate()
  86. {
  87. ProcessSendQueue(state);
  88. });
  89. senderThread.IsBackground = true;
  90. senderThread.Start();
  91. try
  92. {
  93. clientSocket.BeginReceive(state.ReceiveBuffer, 0, ConnectionState.ReceiveBufferSize, 0, ReceiveCallback, state);
  94. }
  95. catch (ObjectDisposedException)
  96. {
  97. Log(Severity.Debug, "[OnConnectRequest] BeginReceive ObjectDisposedException");
  98. }
  99. catch (SocketException ex)
  100. {
  101. Log(Severity.Debug, "[OnConnectRequest] BeginReceive SocketException: {0}", ex.Message);
  102. }
  103. m_listenerSocket.BeginAccept(ConnectRequestCallback, m_listenerSocket);
  104. }
  105. public void Stop()
  106. {
  107. Log(Severity.Information, "Stopping Server");
  108. m_listening = false;
  109. SocketUtils.ReleaseSocket(m_listenerSocket);
  110. }
  111. private void ReceiveCallback(IAsyncResult result)
  112. {
  113. if (!m_listening)
  114. {
  115. return;
  116. }
  117. ConnectionState state = (ConnectionState)result.AsyncState;
  118. Socket clientSocket = state.ClientSocket;
  119. if (!clientSocket.Connected)
  120. {
  121. return;
  122. }
  123. int numberOfBytesReceived;
  124. try
  125. {
  126. numberOfBytesReceived = clientSocket.EndReceive(result);
  127. }
  128. catch (ObjectDisposedException)
  129. {
  130. Log(Severity.Debug, "[ReceiveCallback] EndReceive ObjectDisposedException");
  131. return;
  132. }
  133. catch (SocketException ex)
  134. {
  135. Log(Severity.Debug, "[ReceiveCallback] EndReceive SocketException: {0}", ex.Message);
  136. return;
  137. }
  138. if (numberOfBytesReceived == 0)
  139. {
  140. // The other side has closed the connection
  141. clientSocket.Close();
  142. Log(Severity.Verbose, "The initiator has closed the connection");
  143. // Wait for pending I/O to complete.
  144. state.RunningSCSICommands.WaitUntilZero();
  145. state.SendQueue.Stop();
  146. m_connectionManager.RemoveConnection(state);
  147. return;
  148. }
  149. byte[] currentBuffer = ByteReader.ReadBytes(state.ReceiveBuffer, 0, numberOfBytesReceived);
  150. ProcessCurrentBuffer(currentBuffer, state);
  151. try
  152. {
  153. clientSocket.BeginReceive(state.ReceiveBuffer, 0, ConnectionState.ReceiveBufferSize, 0, ReceiveCallback, state);
  154. }
  155. catch (ObjectDisposedException)
  156. {
  157. Log(Severity.Debug, "[ReceiveCallback] BeginReceive ObjectDisposedException");
  158. }
  159. catch (SocketException ex)
  160. {
  161. Log(Severity.Debug, "[ReceiveCallback] BeginReceive SocketException: {0}", ex.Message);
  162. }
  163. }
  164. private void ProcessCurrentBuffer(byte[] currentBuffer, ConnectionState state)
  165. {
  166. Socket clientSocket = state.ClientSocket;
  167. if (state.ConnectionBuffer.Length == 0)
  168. {
  169. state.ConnectionBuffer = currentBuffer;
  170. }
  171. else
  172. {
  173. state.ConnectionBuffer = ByteUtils.Concatenate(state.ConnectionBuffer, currentBuffer);
  174. }
  175. // we now have all PDU bytes received so far in state.ConnectionBuffer
  176. int bytesLeftInBuffer = state.ConnectionBuffer.Length;
  177. while (bytesLeftInBuffer >= 8)
  178. {
  179. int bufferOffset = state.ConnectionBuffer.Length - bytesLeftInBuffer;
  180. int pduLength = ISCSIPDU.GetPDULength(state.ConnectionBuffer, bufferOffset);
  181. if (pduLength > bytesLeftInBuffer)
  182. {
  183. Log(Severity.Debug, "[{0}][ProcessCurrentBuffer] Bytes left in receive buffer: {1}", state.ConnectionIdentifier, bytesLeftInBuffer);
  184. break;
  185. }
  186. else
  187. {
  188. byte[] pduBytes = ByteReader.ReadBytes(state.ConnectionBuffer, bufferOffset, pduLength);
  189. bytesLeftInBuffer -= pduLength;
  190. ISCSIPDU pdu = null;
  191. try
  192. {
  193. pdu = ISCSIPDU.GetPDU(pduBytes);
  194. }
  195. catch (Exception ex)
  196. {
  197. Log(Severity.Error, "[{0}] Failed to read PDU (Exception: {1})", state.ConnectionIdentifier, ex.Message);
  198. RejectPDU reject = new RejectPDU();
  199. reject.Reason = RejectReason.InvalidPDUField;
  200. reject.Data = ByteReader.ReadBytes(pduBytes, 0, 48);
  201. state.SendQueue.Enqueue(reject);
  202. }
  203. if (pdu != null)
  204. {
  205. if (pdu.GetType() == typeof(ISCSIPDU))
  206. {
  207. Log(Severity.Error, "[{0}][ProcessCurrentBuffer] Unsupported PDU (0x{1})", state.ConnectionIdentifier, pdu.OpCode.ToString("X"));
  208. // Unsupported PDU
  209. RejectPDU reject = new RejectPDU();
  210. reject.InitiatorTaskTag = pdu.InitiatorTaskTag;
  211. reject.Reason = RejectReason.CommandNotSupported;
  212. reject.Data = ByteReader.ReadBytes(pduBytes, 0, 48);
  213. state.SendQueue.Enqueue(reject);
  214. }
  215. else
  216. {
  217. ProcessPDU(pdu, state);
  218. }
  219. }
  220. if (!clientSocket.Connected)
  221. {
  222. // Do not continue to process the buffer if the other side closed the connection
  223. if (bytesLeftInBuffer > 0)
  224. {
  225. Log(Severity.Debug, "[{0}] Buffer processing aborted, bytes left in receive buffer: {1}", state.ConnectionIdentifier, bytesLeftInBuffer);
  226. }
  227. return;
  228. }
  229. }
  230. }
  231. if (bytesLeftInBuffer > 0)
  232. {
  233. state.ConnectionBuffer = ByteReader.ReadBytes(state.ConnectionBuffer, state.ConnectionBuffer.Length - bytesLeftInBuffer, bytesLeftInBuffer);
  234. }
  235. else
  236. {
  237. state.ConnectionBuffer = new byte[0];
  238. }
  239. }
  240. private void ProcessPDU(ISCSIPDU pdu, ConnectionState state)
  241. {
  242. Socket clientSocket = state.ClientSocket;
  243. uint? cmdSN = PDUHelper.GetCmdSN(pdu);
  244. Log(Severity.Trace, "Entering ProcessPDU");
  245. Log(Severity.Verbose, "[{0}] Received PDU from initiator, Operation: {1}, Size: {2}, CmdSN: {3}", state.ConnectionIdentifier, (ISCSIOpCodeName)pdu.OpCode, pdu.Length, cmdSN);
  246. // RFC 3720: On any connection, the iSCSI initiator MUST send the commands in increasing order of CmdSN,
  247. // except for commands that are retransmitted due to digest error recovery and connection recovery.
  248. if (cmdSN.HasValue)
  249. {
  250. if (state.SessionParameters.CommandNumberingStarted)
  251. {
  252. if (cmdSN != state.SessionParameters.ExpCmdSN)
  253. {
  254. Log(Severity.Error, "[{0}] CmdSN outside of expected range", state.ConnectionIdentifier);
  255. // We ignore this PDU
  256. Log(Severity.Trace, "Leaving ProcessPDU");
  257. return;
  258. }
  259. }
  260. else
  261. {
  262. state.SessionParameters.ExpCmdSN = cmdSN.Value;
  263. state.SessionParameters.CommandNumberingStarted = true;
  264. }
  265. if (pdu is LogoutRequestPDU || pdu is TextRequestPDU || pdu is SCSICommandPDU || pdu is RejectPDU)
  266. {
  267. if (!pdu.ImmediateDelivery)
  268. {
  269. state.SessionParameters.ExpCmdSN++;
  270. }
  271. }
  272. }
  273. if (!state.SessionParameters.IsFullFeaturePhase)
  274. {
  275. if (pdu is LoginRequestPDU)
  276. {
  277. LoginRequestPDU request = (LoginRequestPDU)pdu;
  278. Log(Severity.Verbose, "[{0}] Login Request, current stage: {1}, next stage: {2}, parameters: {3}", state.ConnectionIdentifier, request.CurrentStage, request.NextStage, KeyValuePairUtils.ToString(request.LoginParameters));
  279. if (request.TSIH != 0)
  280. {
  281. // RFC 3720: A Login Request with a non-zero TSIH and a CID equal to that of an existing
  282. // connection implies a logout of the connection followed by a Login
  283. ConnectionState existingConnection = m_connectionManager.FindConnection(request.ISID, request.TSIH, request.CID);
  284. if (existingConnection != null)
  285. {
  286. // Perform implicit logout
  287. Log(Severity.Verbose, "[{0}] Initiating implicit logout", state.ConnectionIdentifier);
  288. // Wait for pending I/O to complete.
  289. existingConnection.RunningSCSICommands.WaitUntilZero();
  290. SocketUtils.ReleaseSocket(existingConnection.ClientSocket);
  291. existingConnection.SendQueue.Stop();
  292. m_connectionManager.RemoveConnection(existingConnection);
  293. Log(Severity.Verbose, "[{0}] Implicit logout completed", state.ConnectionIdentifier);
  294. }
  295. }
  296. LoginResponsePDU response = ServerResponseHelper.GetLoginResponsePDU(request, m_targets, state.SessionParameters, state.ConnectionParameters, ref state.Target, GetNextTSIH);
  297. if (state.SessionParameters.IsFullFeaturePhase)
  298. {
  299. state.SessionParameters.ISID = request.ISID;
  300. state.ConnectionParameters.CID = request.CID;
  301. m_connectionManager.AddConnection(state);
  302. }
  303. Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, KeyValuePairUtils.ToString(response.LoginParameters));
  304. state.SendQueue.Enqueue(response);
  305. }
  306. else
  307. {
  308. // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
  309. Log(Severity.Error, "[{0}] Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
  310. if (state.SessionParameters.TSIH == 0)
  311. {
  312. // A target receiving any PDU except a Login request before the Login phase is started MUST
  313. // immediately terminate the connection on which the PDU was received.
  314. clientSocket.Close();
  315. }
  316. else
  317. {
  318. // Once the Login phase has started, if the target receives any PDU except a Login request,
  319. // it MUST send a Login reject (with Status "invalid during login") and then disconnect.
  320. LoginResponsePDU loginResponse = new LoginResponsePDU();
  321. loginResponse.TSIH = state.SessionParameters.TSIH;
  322. loginResponse.Status = LoginResponseStatusName.InvalidDuringLogon;
  323. state.SendQueue.Enqueue(loginResponse);
  324. }
  325. }
  326. }
  327. else // Logged in
  328. {
  329. if (pdu is TextRequestPDU)
  330. {
  331. TextRequestPDU request = (TextRequestPDU)pdu;
  332. TextResponsePDU response = ServerResponseHelper.GetTextResponsePDU(request, m_targets);
  333. state.SendQueue.Enqueue(response);
  334. }
  335. else if (pdu is LogoutRequestPDU)
  336. {
  337. Log(Severity.Verbose, "[{0}] Logour Request", state.ConnectionIdentifier);
  338. LogoutRequestPDU request = (LogoutRequestPDU)pdu;
  339. if (state.SessionParameters.IsDiscovery && request.ReasonCode != LogoutReasonCode.CloseTheSession)
  340. {
  341. // RFC 3720: Discovery-session: The target MUST ONLY accept [..] logout request with the reason "close the session"
  342. RejectPDU reject = new RejectPDU();
  343. reject.Reason = RejectReason.ProtocolError;
  344. reject.Data = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);
  345. state.SendQueue.Enqueue(reject);
  346. }
  347. else
  348. {
  349. List<ConnectionState> connectionsToClose = new List<ConnectionState>();
  350. if (request.ReasonCode == LogoutReasonCode.CloseTheSession)
  351. {
  352. connectionsToClose = m_connectionManager.GetSessionConnections(state.SessionParameters.ISID, state.SessionParameters.TSIH);
  353. }
  354. else
  355. {
  356. // RFC 3720: A Logout for a CID may be performed on a different transport connection when the TCP connection for the CID has already been terminated.
  357. ConnectionState existingConnection = m_connectionManager.FindConnection(state.SessionParameters.ISID, state.SessionParameters.TSIH, request.CID);
  358. if (existingConnection != null && existingConnection != state)
  359. {
  360. connectionsToClose.Add(existingConnection);
  361. }
  362. connectionsToClose.Add(state);
  363. }
  364. foreach (ConnectionState connection in connectionsToClose)
  365. {
  366. // Wait for pending I/O to complete.
  367. connection.RunningSCSICommands.WaitUntilZero();
  368. if (connection != state)
  369. {
  370. SocketUtils.ReleaseSocket(connection.ClientSocket);
  371. }
  372. m_connectionManager.RemoveConnection(connection);
  373. }
  374. LogoutResponsePDU response = ServerResponseHelper.GetLogoutResponsePDU(request);
  375. state.SendQueue.Enqueue(response);
  376. // connection will be closed after a LogoutResponsePDU has been sent.
  377. }
  378. }
  379. else if (state.SessionParameters.IsDiscovery)
  380. {
  381. // The target MUST ONLY accept text requests with the SendTargets key and a logout
  382. // request with the reason "close the session". All other requests MUST be rejected.
  383. Log(Severity.Error, "[{0}] Improper command during discovery session, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
  384. RejectPDU reject = new RejectPDU();
  385. reject.Reason = RejectReason.ProtocolError;
  386. reject.Data = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);
  387. state.SendQueue.Enqueue(reject);
  388. }
  389. else if (pdu is NOPOutPDU)
  390. {
  391. NOPOutPDU request = (NOPOutPDU)pdu;
  392. if (request.InitiatorTaskTag != 0xFFFFFFFF)
  393. {
  394. NOPInPDU response = ServerResponseHelper.GetNOPResponsePDU(request);
  395. state.SendQueue.Enqueue(response);
  396. }
  397. }
  398. else if (pdu is SCSIDataOutPDU || pdu is SCSICommandPDU)
  399. {
  400. // RFC 3720: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engine) in the order specified by CmdSN.
  401. // e.g. read requests should not be executed while previous write request data is being received (via R2T)
  402. List<SCSICommandPDU> commandsToExecute = null;
  403. List<ReadyToTransferPDU> readyToTransferPDUs = new List<ReadyToTransferPDU>();
  404. if (pdu is SCSIDataOutPDU)
  405. {
  406. SCSIDataOutPDU request = (SCSIDataOutPDU)pdu;
  407. Log(Severity.Debug, "[{0}] SCSIDataOutPDU: Target transfer tag: {1}, LUN: {2}, Buffer offset: {3}, Data segment length: {4}, DataSN: {5}, Final: {6}", state.ConnectionIdentifier, request.TargetTransferTag, (ushort)request.LUN, request.BufferOffset, request.DataSegmentLength, request.DataSN, request.Final);
  408. try
  409. {
  410. readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(request, state.Target, state.SessionParameters, state.ConnectionParameters, out commandsToExecute);
  411. }
  412. catch (InvalidTargetTransferTagException ex)
  413. {
  414. Log(Severity.Error, "[{0}] Invalid TargetTransferTag: {1}", state.ConnectionIdentifier, ex.TargetTransferTag);
  415. RejectPDU reject = new RejectPDU();
  416. reject.InitiatorTaskTag = request.InitiatorTaskTag;
  417. reject.Reason = RejectReason.InvalidPDUField;
  418. reject.Data = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
  419. state.SendQueue.Enqueue(reject);
  420. }
  421. }
  422. else
  423. {
  424. SCSICommandPDU command = (SCSICommandPDU)pdu;
  425. Log(Severity.Debug, "[{0}] SCSICommandPDU: CmdSN: {1}, LUN: {2}, Data segment length: {3}, Expected Data Transfer Length: {4}, Final: {5}", state.ConnectionIdentifier, command.CmdSN, (ushort)command.LUN, command.DataSegmentLength, command.ExpectedDataTransferLength, command.Final);
  426. readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(command, state.Target, state.SessionParameters, state.ConnectionParameters, out commandsToExecute);
  427. }
  428. foreach (ReadyToTransferPDU readyToTransferPDU in readyToTransferPDUs)
  429. {
  430. state.SendQueue.Enqueue(readyToTransferPDU);
  431. }
  432. if (commandsToExecute != null)
  433. {
  434. state.RunningSCSICommands.Add(commandsToExecute.Count);
  435. }
  436. foreach (SCSICommandPDU commandPDU in commandsToExecute)
  437. {
  438. Log(Severity.Debug, "[{0}] Queuing command: CmdSN: {1}", state.ConnectionIdentifier, commandPDU.CmdSN);
  439. state.Target.QueueCommand(commandPDU.CommandDescriptorBlock, commandPDU.LUN, commandPDU.Data, commandPDU, state.OnCommandCompleted);
  440. }
  441. }
  442. else if (pdu is LoginRequestPDU)
  443. {
  444. Log(Severity.Error, "[{0}] Protocol Error (Login request during full feature phase)", state.ConnectionIdentifier);
  445. // RFC 3720: Login requests and responses MUST be used exclusively during Login.
  446. // On any connection, the login phase MUST immediately follow TCP connection establishment and
  447. // a subsequent Login Phase MUST NOT occur before tearing down a connection
  448. RejectPDU reject = new RejectPDU();
  449. reject.Reason = RejectReason.ProtocolError;
  450. reject.Data = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);
  451. state.SendQueue.Enqueue(reject);
  452. }
  453. else
  454. {
  455. Log(Severity.Error, "[{0}] Unsupported command, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
  456. RejectPDU reject = new RejectPDU();
  457. reject.Reason = RejectReason.CommandNotSupported;
  458. reject.Data = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);
  459. state.SendQueue.Enqueue(reject);
  460. }
  461. }
  462. Log(Severity.Trace, "Leaving ProcessPDU");
  463. }
  464. private void ProcessSendQueue(ConnectionState state)
  465. {
  466. while (true)
  467. {
  468. Log(Severity.Trace, "Entering ProcessSendQueue");
  469. ISCSIPDU response;
  470. bool stopped = !state.SendQueue.TryDequeue(out response);
  471. if (stopped)
  472. {
  473. return;
  474. }
  475. Socket clientSocket = state.ClientSocket;
  476. PDUHelper.SetStatSN(response, state.ConnectionParameters.StatSN);
  477. PDUHelper.SetExpCmdSN(response, state.SessionParameters.ExpCmdSN, state.SessionParameters.ExpCmdSN + state.SessionParameters.CommandQueueSize);
  478. if (response is SCSIResponsePDU ||
  479. response is LoginResponsePDU ||
  480. response is TextResponsePDU ||
  481. (response is SCSIDataInPDU && ((SCSIDataInPDU)response).StatusPresent) ||
  482. response is RejectPDU)
  483. {
  484. state.ConnectionParameters.StatSN++;
  485. }
  486. try
  487. {
  488. clientSocket.Send(response.GetBytes());
  489. Log(Severity.Verbose, "[{0}] Sent response to initator, Operation: {1}, Size: {2}", state.ConnectionIdentifier, response.OpCode, response.Length);
  490. if (response is LogoutResponsePDU)
  491. {
  492. clientSocket.Close(); // We can close the connection now
  493. Log(Severity.Trace, "Leaving ProcessSendQueue");
  494. return;
  495. }
  496. else if (response is LoginResponsePDU)
  497. {
  498. if (((LoginResponsePDU)response).Status == LoginResponseStatusName.InvalidDuringLogon)
  499. {
  500. clientSocket.Close(); // We can close the connection now
  501. Log(Severity.Trace, "Leaving ProcessSendQueue");
  502. return;
  503. }
  504. }
  505. }
  506. catch (SocketException ex)
  507. {
  508. Log(Severity.Verbose, "[{0}] Failed to send response to initator. Operation: {1}, Size: {2}, SocketException: {3}", state.ConnectionIdentifier, response.OpCode, response.Length, ex.Message);
  509. Log(Severity.Trace, "Leaving ProcessSendQueue");
  510. return;
  511. }
  512. catch (ObjectDisposedException)
  513. {
  514. Log(Severity.Verbose, "[{0}] Failed to send response to initator. Operation: {1}, Size: {2}. ObjectDisposedException", state.ConnectionIdentifier, response.OpCode, response.Length);
  515. Log(Severity.Trace, "Leaving ProcessSendQueue");
  516. return;
  517. }
  518. }
  519. }
  520. public void Log(Severity severity, string message)
  521. {
  522. // To be thread-safe we must capture the delegate reference first
  523. EventHandler<LogEntry> handler = OnLogEntry;
  524. if (handler != null)
  525. {
  526. handler(this, new LogEntry(DateTime.Now, severity, "iSCSI Server", message));
  527. }
  528. }
  529. public void Log(Severity severity, string message, params object[] args)
  530. {
  531. Log(severity, String.Format(message, args));
  532. }
  533. public ushort GetNextTSIH()
  534. {
  535. // The iSCSI Target selects a non-zero value for the TSIH at
  536. // session creation (when an initiator presents a 0 value at Login).
  537. // After being selected, the same TSIH value MUST be used whenever the
  538. // initiator or target refers to the session and a TSIH is required
  539. ushort nextTSIH = m_nextTSIH;
  540. m_nextTSIH++;
  541. if (m_nextTSIH == 0)
  542. {
  543. m_nextTSIH++;
  544. }
  545. return nextTSIH;
  546. }
  547. }
  548. }