Files
gotgt/pkg/port/iscsit/iscsid.go
2026-03-14 11:45:35 +08:00

1219 lines
32 KiB
Go

/*
Copyright 2017 The GoStor Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package iscsit
import (
"fmt"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/config"
"github.com/gostor/gotgt/pkg/scsi"
"github.com/gostor/gotgt/pkg/util"
log "github.com/sirupsen/logrus"
)
const (
ISCSI_MAX_TSIH = uint16(0xffff)
ISCSI_UNSPEC_TSIH = uint16(0)
)
const (
STATE_INIT = iota
STATE_RUNNING
STATE_SHUTTING_DOWN
STATE_TERMINATE
)
// tsihBitmap is a bitmap for efficient TSIH allocation/deallocation
// Uses circular counter for O(1) allocation
type tsihBitmap struct {
mu sync.Mutex
bitmap []uint64 // Each uint64 stores the usage status of 64 TSIHs
next uint16 // Next candidate position for allocation
used uint16 // Number of used TSIHs
}
// newTSIHBitmap creates a new TSIH bitmap
// Reserves 0 and 65535 as special values
func newTSIHBitmap() *tsihBitmap {
// Need 65536 bits = 1024 uint64s
b := &tsihBitmap{
bitmap: make([]uint64, 1024),
next: 1, // Start from 1, 0 is reserved
}
// Mark 0 and 65535 as used (reserved values)
b.bitmap[0] |= 1 << 0 // TSIH = 0
b.bitmap[1023] |= 1 << 63 // TSIH = 65535
b.used = 2
return b
}
// alloc allocates an available TSIH using circular search strategy
func (b *tsihBitmap) alloc() uint16 {
b.mu.Lock()
defer b.mu.Unlock()
if b.used >= ISCSI_MAX_TSIH-1 {
return ISCSI_UNSPEC_TSIH
}
start := b.next
for {
idx := b.next / 64
bit := b.next % 64
if (b.bitmap[idx] & (1 << bit)) == 0 {
// Found free slot
b.bitmap[idx] |= 1 << bit
b.used++
result := b.next
// Update next to next position
b.next++
if b.next >= ISCSI_MAX_TSIH {
b.next = 1
}
return result
}
b.next++
if b.next >= ISCSI_MAX_TSIH {
b.next = 1
}
if b.next == start {
// Looped around without finding
return ISCSI_UNSPEC_TSIH
}
}
}
// release releases a TSIH
func (b *tsihBitmap) release(tsih uint16) {
if tsih == 0 || tsih == ISCSI_MAX_TSIH {
return // Cannot release reserved values
}
b.mu.Lock()
defer b.mu.Unlock()
idx := tsih / 64
bit := tsih % 64
if (b.bitmap[idx] & (1 << bit)) != 0 {
b.bitmap[idx] &^= 1 << bit
b.used--
}
}
var (
EnableStats bool
CurrentHostIP string
IPMutex sync.Mutex
)
type ISCSITargetDriver struct {
SCSI *scsi.SCSITargetService
Name string
iSCSITargets map[string]*ISCSITarget
tsihBitmap *tsihBitmap
isClientConnected bool
enableStats bool
mu *sync.RWMutex
l net.Listener
state uint8
OpCode int
TargetStats scsi.Stats
clusterIP string
blockMultipleHostLogin bool
}
func init() {
scsi.RegisterTargetDriver(iSCSIDriverName, NewISCSITargetDriver)
}
func NewISCSITargetDriver(base *scsi.SCSITargetService) (scsi.SCSITargetDriver, error) {
driver := &ISCSITargetDriver{
Name: iSCSIDriverName,
iSCSITargets: map[string]*ISCSITarget{},
SCSI: base,
tsihBitmap: newTSIHBitmap(),
mu: &sync.RWMutex{},
}
if EnableStats {
driver.enableStats = true
driver.TargetStats.SCSIIOCount = map[int]int64{}
}
return driver, nil
}
func (s *ISCSITargetDriver) AllocTSIH() uint16 {
return s.tsihBitmap.alloc()
}
func (s *ISCSITargetDriver) ReleaseTSIH(tsih uint16) {
s.tsihBitmap.release(tsih)
}
func (s *ISCSITargetDriver) NewTarget(tgtName string, configInfo *config.Config) error {
if _, ok := s.iSCSITargets[tgtName]; ok {
return fmt.Errorf("target name has been existed")
}
stgt, err := s.SCSI.NewSCSITarget(len(s.iSCSITargets), "iscsi", tgtName)
if err != nil {
return err
}
tgt := newISCSITarget(stgt)
s.iSCSITargets[tgtName] = tgt
scsiTPG := tgt.SCSITarget.TargetPortGroups[0]
targetConfig := configInfo.ISCSITargets[tgtName]
for tpgt, portalIDArrary := range targetConfig.TPGTs {
tpgtNumber, _ := strconv.ParseUint(tpgt, 10, 16)
tgt.TPGTs[uint16(tpgtNumber)] = &iSCSITPGT{TPGT: uint16(tpgtNumber), Portals: make(map[string]struct{})}
targetPortName := fmt.Sprintf("%s,t,0x%02x", tgtName, tpgtNumber)
scsiTPG.TargetPortGroup = append(scsiTPG.TargetPortGroup, &api.SCSITargetPort{RelativeTargetPortID: uint16(tpgtNumber), TargetPortName: targetPortName})
for _, portalID := range portalIDArrary {
portal := configInfo.ISCSIPortals[portalID]
s.AddiSCSIPortal(tgtName, uint16(tpgtNumber), portal.Portal)
}
}
return nil
}
func (s *ISCSITargetDriver) SetClusterIP(ip string) {
s.clusterIP = ip
}
func (s *ISCSITargetDriver) EnableBlockMultipleHostLogin() {
s.blockMultipleHostLogin = true
}
func (s *ISCSITargetDriver) RereadTargetLUNMap() {
s.SCSI.RereadTargetLUNMap()
}
func (s *ISCSITargetDriver) AddiSCSIPortal(tgtName string, tpgt uint16, portal string) error {
var (
ok bool
target *ISCSITarget
tpgtInfo *iSCSITPGT
)
if target, ok = s.iSCSITargets[tgtName]; !ok {
return fmt.Errorf("No such target: %s", tgtName)
}
if tpgtInfo, ok = target.TPGTs[tpgt]; !ok {
return fmt.Errorf("No such TPGT: %d", tpgt)
}
tgtPortals := tpgtInfo.Portals
if _, ok = tgtPortals[portal]; !ok {
tgtPortals[portal] = struct{}{}
} else {
return fmt.Errorf("duplicate portal %s,in %s,%d", portal, tgtName, tpgt)
}
return nil
}
func (s *ISCSITargetDriver) HasPortal(tgtName string, tpgt uint16, portal string) bool {
var (
ok bool
target *ISCSITarget
tpgtInfo *iSCSITPGT
)
if target, ok = s.iSCSITargets[tgtName]; !ok {
return false
}
if tpgtInfo, ok = target.TPGTs[tpgt]; !ok {
return false
}
tgtPortals := tpgtInfo.Portals
if _, ok = tgtPortals[portal]; !ok {
return false
} else {
return true
}
}
func (s *ISCSITargetDriver) Run(port int) error {
l, err := net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
log.Error(err)
os.Exit(1)
}
s.mu.Lock()
s.l = l
s.mu.Unlock()
log.Infof("iSCSI service listening on: %v", s.l.Addr())
s.setState(STATE_RUNNING)
for {
conn, err := l.Accept()
if err != nil {
if err, ok := err.(net.Error); ok {
if !err.Temporary() {
log.Warning("Closing connection with initiator...")
break
}
}
log.Error(err)
continue
}
remoteIP := strings.Split(conn.RemoteAddr().String(), ":")[0]
IPMutex.Lock()
if CurrentHostIP == "" {
CurrentHostIP = remoteIP
}
IPMutex.Unlock()
if s.blockMultipleHostLogin && remoteIP != CurrentHostIP {
conn.Close()
log.Infof("rejecting connection: %s target already connected at %s",
remoteIP, CurrentHostIP)
continue
}
log.Info("connection establishing at: ", conn.LocalAddr().String())
s.setClientStatus(true)
iscsiConn := &iscsiConnection{conn: conn,
loginParam: &iscsiLoginParam{}}
iscsiConn.init()
iscsiConn.rxIOState = IOSTATE_RX_BHS
log.Infof("Target is connected to initiator: %s", conn.RemoteAddr().String())
// start a new thread to do with this command
go s.handler(DATAIN, iscsiConn)
}
return nil
}
func (s *ISCSITargetDriver) setClientStatus(ok bool) {
s.isClientConnected = ok
}
func (s *ISCSITargetDriver) isInitiatorConnected() bool {
return s.isClientConnected
}
func (s *ISCSITargetDriver) Close() error {
s.mu.Lock()
l := s.l
s.setClientStatus(false)
s.mu.Unlock()
if l != nil {
s.setState(STATE_SHUTTING_DOWN)
if err := l.Close(); err != nil {
return err
}
s.setState(STATE_TERMINATE)
return nil
}
return nil
}
func (s *ISCSITargetDriver) setState(st uint8) {
s.mu.Lock()
defer s.mu.Unlock()
s.state = st
}
func (s *ISCSITargetDriver) Resize(size uint64) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.SCSI.Resize(size)
}
func (s *ISCSITargetDriver) handler(events byte, conn *iscsiConnection) {
if events&DATAIN != 0 {
log.Debug("rx handler processing...")
go func() {
s.rxHandler(conn)
if conn.state == CONN_STATE_CLOSE {
log.Warningf("iscsi connection[%d] closed", conn.cid)
conn.close()
IPMutex.Lock()
remoteIP := strings.Split(conn.conn.RemoteAddr().String(), ":")[0]
if CurrentHostIP == remoteIP {
CurrentHostIP = ""
}
IPMutex.Unlock()
}
}()
}
if conn.state != CONN_STATE_CLOSE && events&DATAOUT != 0 {
log.Debug("tx handler processing...")
s.txHandler(conn)
}
if conn.state == CONN_STATE_CLOSE {
log.Warningf("iscsi connection[%d] closed", conn.cid)
conn.close()
IPMutex.Lock()
remoteIP := strings.Split(conn.conn.RemoteAddr().String(), ":")[0]
if CurrentHostIP == remoteIP {
CurrentHostIP = ""
}
IPMutex.Unlock()
}
}
func (s *ISCSITargetDriver) rxHandler(conn *iscsiConnection) {
var (
hdigest uint = 0
ddigest uint = 0
final bool = false
cmd *ISCSICommand
buf []byte = getBuffer()
length int
err error
)
defer putBuffer(buf)
conn.readLock.Lock()
defer conn.readLock.Unlock()
if conn.state == CONN_STATE_SCSI {
hdigest = conn.loginParam.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C
ddigest = conn.loginParam.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C
}
for {
switch conn.rxIOState {
case IOSTATE_RX_BHS:
log.Debug("rx handler: IOSTATE_RX_BHS")
length, err = conn.readData(buf)
if err != nil {
log.Error("read BHS failed: ", err)
conn.state = CONN_STATE_CLOSE
return
}
if length == 0 {
log.Warningf("set connection to close")
conn.state = CONN_STATE_CLOSE
return
}
cmd, err = parseHeader(buf)
if err != nil {
log.Error(err)
log.Warningf("set connection to close")
conn.state = CONN_STATE_CLOSE
return
}
conn.req = cmd
if length == BHS_SIZE && cmd.DataLen != 0 {
conn.rxIOState = IOSTATE_RX_INIT_AHS
break
}
if log.GetLevel() == log.DebugLevel {
log.Debugf("got command: \n%s", cmd.String())
log.Debugf("got buffer: %v", buf)
}
final = true
case IOSTATE_RX_INIT_AHS:
if hdigest != 0 {
conn.rxIOState = IOSTATE_RX_INIT_HDIGEST
} else {
conn.rxIOState = IOSTATE_RX_DATA
}
case IOSTATE_RX_DATA:
if ddigest != 0 {
conn.rxIOState = IOSTATE_RX_INIT_DDIGEST
}
if cmd == nil {
return
}
dl := ((cmd.DataLen + DataPadding - 1) / DataPadding) * DataPadding
cmd.RawData = make([]byte, int(dl))
length := 0
for length < dl {
l, err := conn.readData(cmd.RawData[length:])
if err != nil {
log.Error("read data failed:", err)
conn.state = CONN_STATE_CLOSE
return
}
length += l
}
if length != dl {
log.Debugf("get length is %d, but expected %d", length, dl)
log.Warning("set connection to close")
conn.state = CONN_STATE_CLOSE
return
}
final = true
default:
log.Errorf("error %d %d\n", conn.state, conn.rxIOState)
return
}
if final {
break
}
}
if conn.state == CONN_STATE_SCSI {
s.scsiCommandHandler(conn)
} else {
conn.txIOState = IOSTATE_TX_BHS
conn.resp = &ISCSICommand{}
switch conn.req.OpCode {
case OpLoginReq:
log.Debug("OpLoginReq")
if err := s.iscsiExecLogin(conn); err != nil {
log.Error(err)
log.Warningf("set connection to close")
conn.state = CONN_STATE_CLOSE
}
case OpLogoutReq:
log.Debug("OpLogoutReq")
s.setClientStatus(false)
if err := iscsiExecLogout(conn); err != nil {
log.Warningf("set connection to close")
conn.state = CONN_STATE_CLOSE
}
case OpTextReq:
log.Debug("OpTextReq")
if err := s.iscsiExecText(conn); err != nil {
log.Warningf("set connection to close")
conn.state = CONN_STATE_CLOSE
}
default:
iscsiExecReject(conn)
}
log.Debugf("connection state is %v", conn.State())
s.handler(DATAOUT, conn)
}
}
func (s *ISCSITargetDriver) iscsiExecLogin(conn *iscsiConnection) error {
var cmd = conn.req
conn.cid = cmd.ConnID
conn.loginParam.iniCSG = cmd.CSG
conn.loginParam.iniNSG = cmd.NSG
conn.loginParam.iniCont = cmd.Cont
conn.loginParam.iniTrans = cmd.Transit
conn.loginParam.isid = cmd.ISID
conn.loginParam.tsih = cmd.TSIH
conn.expCmdSN = cmd.CmdSN
conn.maxBurstLength = MaxBurstLength
conn.maxRecvDataSegmentLength = MaxRecvDataSegmentLength
conn.maxSeqCount = conn.maxBurstLength / conn.maxRecvDataSegmentLength
if conn.loginParam.iniCSG == SecurityNegotiation {
if err := conn.processSecurityData(); err != nil {
return err
}
conn.state = CONN_STATE_LOGIN
return conn.buildRespPackage(OpLoginResp, nil)
}
if _, err := conn.processLoginData(); err != nil {
return err
}
if !conn.loginParam.paramInit {
if err := s.BindISCSISession(conn); err != nil {
conn.state = CONN_STATE_EXIT
return err
}
conn.loginParam.paramInit = true
}
if conn.loginParam.tgtNSG == FullFeaturePhase &&
conn.loginParam.tgtTrans {
conn.state = CONN_STATE_LOGIN_FULL
} else {
conn.state = CONN_STATE_LOGIN
}
return conn.buildRespPackage(OpLoginResp, nil)
}
func iscsiExecLogout(conn *iscsiConnection) error {
log.Infof("Logout request received from initiator: %v", conn.conn.RemoteAddr().String())
cmd := conn.req
conn.resp = &ISCSICommand{
OpCode: OpLogoutResp,
StatSN: cmd.ExpStatSN,
TaskTag: cmd.TaskTag,
}
if conn.session == nil {
conn.resp.ExpCmdSN = cmd.CmdSN
conn.resp.MaxCmdSN = cmd.CmdSN
} else {
conn.resp.ExpCmdSN = conn.session.ExpCmdSN
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
}
IPMutex.Lock()
remoteIP := strings.Split(conn.conn.RemoteAddr().String(), ":")[0]
if CurrentHostIP == remoteIP {
CurrentHostIP = ""
}
IPMutex.Unlock()
return nil
}
func (s *ISCSITargetDriver) iscsiExecText(conn *iscsiConnection) error {
var result = []util.KeyValue{}
cmd := conn.req
keys := util.ParseKVText(cmd.RawData)
if st, ok := keys["SendTargets"]; ok {
if st == "All" {
for name, tgt := range s.iSCSITargets {
log.Debugf("iscsi target: %v", name)
//log.Debugf("iscsi target portals: %v", tgt.Portals)
result = append(result, util.KeyValue{
Key: "TargetName",
Value: name,
})
if s.clusterIP == "" {
for _, tpgt := range tgt.TPGTs {
for portal := range tpgt.Portals {
targetPort := fmt.Sprintf("%s,%d", portal, tpgt.TPGT)
result = append(result, util.KeyValue{
Key: "TargetAddress",
Value: targetPort,
})
}
}
} else {
for _, tpgt := range tgt.TPGTs {
targetPort := fmt.Sprintf("%s,%d", s.clusterIP, tpgt.TPGT)
result = append(result, util.KeyValue{
Key: "TargetAddress",
Value: targetPort,
})
}
}
}
}
}
conn.resp = &ISCSICommand{
OpCode: OpTextResp,
Final: true,
NSG: FullFeaturePhase,
StatSN: cmd.ExpStatSN,
TaskTag: cmd.TaskTag,
ExpCmdSN: cmd.CmdSN,
MaxCmdSN: cmd.CmdSN,
}
conn.resp.RawData = util.MarshalKVText(result)
return nil
}
func iscsiExecNoopOut(conn *iscsiConnection) error {
return conn.buildRespPackage(OpNoopIn, nil)
}
// SNACK Type constants per RFC 7143
const (
SNACK_TYPE_DATA_ACK = 0 // Data ACK
SNACK_TYPE_STATUS_ACK = 1 // Status ACK
SNACK_TYPE_DATA_R2T = 2 // Data R2T
SNACK_TYPE_R_DATA = 3 // R-Data
)
/*
* iscsiExecSNACK handles SNACK (Sequence Number Acknowledgement) requests
* SNACK is used for error recovery in iSCSI protocol per RFC 7143 section 11.9
*/
func (s *ISCSITargetDriver) iscsiExecSNACK(conn *iscsiConnection) error {
req := conn.req
// Parse SNACK type from byte 1, bits 0-1
snackType := (req.SCSIOpCode >> 0) & 0x03
// Parse BegRun and RunLength from the header
begRun := req.ReferencedTaskTag
runLength := req.R2TSN
log.Debugf("SNACK request type=%d, BegRun=%d, RunLength=%d", snackType, begRun, runLength)
switch snackType {
case SNACK_TYPE_DATA_ACK:
// Data ACK - initiator acknowledges receipt of Data-In PDUs
// For ErrorRecoveryLevel >= 1, we could track acknowledged Data-In
log.Debug("SNACK Data ACK received")
// Simply return success for now
conn.resp = &ISCSICommand{
OpCode: OpNoopIn,
Final: true,
TaskTag: req.TaskTag,
StatSN: conn.statSN,
ExpCmdSN: conn.expCmdSN,
}
if conn.session != nil {
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
}
return nil
case SNACK_TYPE_STATUS_ACK:
// Status ACK - initiator acknowledges receipt of status
log.Debug("SNACK Status ACK received")
// Similar to Data ACK, just acknowledge
conn.resp = &ISCSICommand{
OpCode: OpNoopIn,
Final: true,
TaskTag: req.TaskTag,
StatSN: conn.statSN,
ExpCmdSN: conn.expCmdSN,
}
if conn.session != nil {
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
}
return nil
case SNACK_TYPE_DATA_R2T:
// Data R2T - request retransmission of R2T
log.Debug("SNACK Data R2T received - requesting R2T retransmission")
// Find the task and resend R2T
conn.session.PendingTasksMutex.RLock()
task := conn.session.PendingTasks.GetByTag(begRun)
conn.session.PendingTasksMutex.RUnlock()
if task == nil {
log.Errorf("Cannot find task for R2T retransmission, tag=%d", begRun)
return fmt.Errorf("task not found")
}
// Reset R2T state and resend
task.r2tSN = runLength
conn.rxTask = task
return iscsiExecR2T(conn)
case SNACK_TYPE_R_DATA:
// R-Data - request retransmission of Data-In
log.Debug("SNACK R-Data received - requesting Data-In retransmission")
// For now, reject this as it requires complex data buffering
// In a full implementation, we would need to buffer Data-In PDUs
// and retransmit based on BegRun and RunLength
log.Warn("R-Data SNACK not fully implemented")
return fmt.Errorf("R-Data SNACK not supported")
default:
return fmt.Errorf("unknown SNACK type: %d", snackType)
}
}
func iscsiExecReject(conn *iscsiConnection) error {
return conn.buildRespPackage(OpReject, nil)
}
func iscsiExecR2T(conn *iscsiConnection) error {
return conn.buildRespPackage(OpReady, nil)
}
func (s *ISCSITargetDriver) txHandler(conn *iscsiConnection) {
var (
hdigest uint = 0
ddigest uint = 0
offset uint32 = 0
final bool = false
count uint32 = 0
)
if conn.state == CONN_STATE_SCSI {
hdigest = conn.loginParam.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C
ddigest = conn.loginParam.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C
}
if conn.state == CONN_STATE_SCSI && conn.txTask == nil {
err := s.scsiCommandHandler(conn)
if err != nil {
log.Error(err)
return
}
}
resp := conn.resp
segmentLen := conn.maxRecvDataSegmentLength
transferLen := len(resp.RawData)
resp.DataSN = 0
maxCount := conn.maxSeqCount
if s.enableStats {
if resp.OpCode == OpSCSIResp || resp.OpCode == OpSCSIIn {
s.UpdateStats(conn)
}
}
/* send data splitted by segmentLen */
SendRemainingData:
if resp.OpCode == OpSCSIIn {
resp.BufferOffset = offset
if int(offset+segmentLen) < transferLen {
count += 1
if count < maxCount {
resp.FinalInSeq = false
resp.Final = false
} else {
count = 0
resp.FinalInSeq = true
resp.Final = false
}
offset = offset + segmentLen
resp.DataLen = int(segmentLen)
} else {
resp.FinalInSeq = true
resp.Final = true
resp.DataLen = transferLen - int(offset)
}
}
for {
switch conn.txIOState {
case IOSTATE_TX_BHS:
if log.GetLevel() == log.DebugLevel {
log.Debug("ready to write response")
log.Debugf("response is %s", resp.String())
}
if l, err := conn.write(resp.Bytes()); err != nil {
log.Errorf("failed to write data to client: %v", err)
return
} else {
conn.txIOState = IOSTATE_TX_INIT_AHS
log.Debugf("success to write %d length", l)
}
case IOSTATE_TX_INIT_AHS:
if hdigest != 0 {
conn.txIOState = IOSTATE_TX_INIT_HDIGEST
} else {
conn.txIOState = IOSTATE_TX_INIT_DATA
}
if conn.txIOState != IOSTATE_TX_AHS {
final = true
}
case IOSTATE_TX_AHS:
case IOSTATE_TX_INIT_DATA:
final = true
case IOSTATE_TX_DATA:
if ddigest != 0 {
conn.txIOState = IOSTATE_TX_INIT_DDIGEST
}
default:
log.Errorf("error %d %d\n", conn.state, conn.txIOState)
return
}
if final {
if resp.OpCode == OpSCSIIn && resp.Final != true {
resp.DataSN++
conn.txIOState = IOSTATE_TX_BHS
goto SendRemainingData
} else {
break
}
}
}
log.Debugf("connection state: %v", conn.State())
switch conn.state {
case CONN_STATE_CLOSE, CONN_STATE_EXIT:
conn.state = CONN_STATE_CLOSE
case CONN_STATE_SECURITY_LOGIN:
conn.state = CONN_STATE_LOGIN
case CONN_STATE_LOGIN:
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
case CONN_STATE_SECURITY_FULL, CONN_STATE_LOGIN_FULL:
if conn.session.SessionType == SESSION_NORMAL {
conn.state = CONN_STATE_KERNEL
conn.state = CONN_STATE_SCSI
} else {
conn.state = CONN_STATE_FULL
}
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
case CONN_STATE_SCSI:
conn.txTask = nil
default:
log.Warnf("unexpected connection state: %v", conn.State())
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
}
}
func (s *ISCSITargetDriver) scsiCommandHandler(conn *iscsiConnection) (err error) {
req := conn.req
switch req.OpCode {
case OpSCSICmd:
log.Debugf("SCSI Command processing...")
scmd := &api.SCSICommand{
ITNexusID: conn.session.ITNexus.ID,
SCB: req.CDB,
SCBLength: len(req.CDB),
Lun: req.LUN,
Tag: uint64(req.TaskTag),
RelTargetPortID: conn.session.TPGT,
}
if req.Read {
if req.Write {
scmd.Direction = api.SCSIDataBidirection
} else {
scmd.Direction = api.SCSIDataRead
}
} else {
if req.Write {
scmd.Direction = api.SCSIDataWrite
}
}
task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: scmd}
task.scmd.OpCode = conn.req.SCSIOpCode
if scmd.Direction == api.SCSIDataBidirection {
task.scmd.Result = api.SAMStatCheckCondition.Stat
scsi.BuildSenseData(task.scmd, scsi.ILLEGAL_REQUEST, scsi.NO_ADDITIONAL_SENSE)
conn.buildRespPackage(OpSCSIResp, task)
conn.rxTask = nil
break
}
if req.Write {
task.r2tCount = int(req.ExpectedDataLen) - req.DataLen
task.expectedDataLength = int64(req.ExpectedDataLen)
if !req.Final {
task.unsolCount = 1
}
// new buffer for the data out
if scmd.OutSDBBuffer == nil {
blen := int(req.ExpectedDataLen)
if blen == 0 {
blen = int(req.DataLen)
}
scmd.OutSDBBuffer = &api.SCSIDataBuffer{
Length: uint32(blen),
Buffer: make([]byte, blen),
}
}
log.Debugf("SCSI write, R2T count: %d, unsol Count: %d, offset: %d", task.r2tCount, task.unsolCount, task.offset)
if conn.session.SessionParam[ISCSI_PARAM_IMM_DATA_EN].Value == 1 {
copy(scmd.OutSDBBuffer.Buffer[task.offset:], conn.req.RawData)
task.offset += conn.req.DataLen
}
if task.r2tCount > 0 {
// prepare to receive more data
conn.session.ExpCmdSN += 1
task.state = taskPending
conn.session.PendingTasksMutex.Lock()
conn.session.PendingTasks.Push(task)
conn.session.PendingTasksMutex.Unlock()
conn.rxTask = task
if conn.session.SessionParam[ISCSI_PARAM_INITIAL_R2T_EN].Value == 1 {
iscsiExecR2T(conn)
break
} else {
log.Debugf("Not ready to exec the task")
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
return nil
}
}
} else if scmd.InSDBBuffer == nil {
scmd.InSDBBuffer = &api.SCSIDataBuffer{
Length: uint32(req.ExpectedDataLen),
Buffer: make([]byte, int(req.ExpectedDataLen)),
}
}
task.offset = 0
conn.rxTask = task
if err = s.iscsiTaskQueueHandler(task); err != nil {
if task.state == taskPending {
s.handler(DATAIN, conn)
err = nil
}
return
} else {
if scmd.Direction == api.SCSIDataRead && scmd.SenseBuffer == nil && req.ExpectedDataLen != 0 {
conn.buildRespPackage(OpSCSIIn, task)
} else {
conn.buildRespPackage(OpSCSIResp, task)
}
conn.rxTask = nil
}
case OpSCSITaskReq:
// task management function
task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: nil}
conn.rxTask = task
if err = s.iscsiTaskQueueHandler(task); err != nil {
return
}
case OpSCSIOut:
log.Debugf("iSCSI Data-out processing...")
conn.session.PendingTasksMutex.RLock()
task := conn.session.PendingTasks.GetByTag(conn.req.TaskTag)
conn.session.PendingTasksMutex.RUnlock()
if task == nil {
err = fmt.Errorf("Cannot find iSCSI task with tag[%v]", conn.req.TaskTag)
log.Error(err)
return
}
copy(task.scmd.OutSDBBuffer.Buffer[task.offset:], conn.req.RawData)
task.offset += conn.req.DataLen
task.r2tCount = task.r2tCount - conn.req.DataLen
log.Debugf("Final: %v", conn.req.Final)
log.Debugf("r2tCount: %v", task.r2tCount)
if !conn.req.Final {
log.Debugf("Not ready to exec the task")
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN, conn)
return nil
} else if task.r2tCount > 0 {
// prepare to receive more data
if task.unsolCount == 0 {
task.r2tSN += 1
} else {
task.r2tSN = 0
task.unsolCount = 0
}
conn.rxTask = task
iscsiExecR2T(conn)
break
}
task.offset = 0
log.Debugf("Process the Data-out package")
conn.rxTask = task
if err = s.iscsiExecTask(task); err != nil {
return
} else {
conn.buildRespPackage(OpSCSIResp, task)
conn.rxTask = nil
conn.session.PendingTasksMutex.Lock()
conn.session.PendingTasks.RemoveByTag(conn.req.TaskTag)
conn.session.PendingTasksMutex.Unlock()
}
case OpNoopOut:
iscsiExecNoopOut(conn)
case OpLogoutReq:
s.setClientStatus(false)
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag}
conn.txIOState = IOSTATE_TX_BHS
iscsiExecLogout(conn)
case OpTextReq:
err = fmt.Errorf("Cannot handle yet %s", opCodeMap[conn.req.OpCode])
log.Error(err)
return
case OpSNACKReq:
log.Debug("SNACK Request processing...")
if err := s.iscsiExecSNACK(conn); err != nil {
log.Errorf("SNACK handling failed: %v", err)
iscsiExecReject(conn)
}
default:
err = fmt.Errorf("Unknown op %s", opCodeMap[conn.req.OpCode])
log.Error(err)
return
}
conn.rxIOState = IOSTATE_RX_BHS
s.handler(DATAIN|DATAOUT, conn)
return nil
}
func (s *ISCSITargetDriver) iscsiTaskQueueHandler(task *iscsiTask) error {
conn := task.conn
sess := conn.session
cmd := task.cmd
if cmd.Immediate {
return s.iscsiExecTask(task)
}
cmdsn := cmd.CmdSN
log.Debugf("CmdSN of command is %d", cmdsn)
if cmdsn == sess.ExpCmdSN {
retry:
cmdsn += 1
sess.ExpCmdSN = cmdsn
log.Debugf("session's ExpCmdSN is %d", cmdsn)
log.Debugf("process task(%d)", task.cmd.CmdSN)
if err := s.iscsiExecTask(task); err != nil {
log.Error(err)
}
sess.PendingTasksMutex.Lock()
if sess.PendingTasks.Len() == 0 {
sess.PendingTasksMutex.Unlock()
return nil
}
task = sess.PendingTasks.Pop()
cmd = task.cmd
if cmd.CmdSN != cmdsn {
sess.PendingTasks.Push(task)
sess.PendingTasksMutex.Unlock()
return nil
}
task.state = taskSCSI
sess.PendingTasksMutex.Unlock()
goto retry
}
// cmd.CmdSN != sess.ExpCmdSN
if cmd.CmdSN < sess.ExpCmdSN {
err := fmt.Errorf("unexpected cmd serial number: (%d, %d)", cmd.CmdSN, sess.ExpCmdSN)
log.Error(err)
return err
}
log.Debugf("add task(%d) into task queue", task.cmd.CmdSN)
// add this task into queue and set it as a pending task
sess.PendingTasksMutex.Lock()
task.state = taskPending
sess.PendingTasks.Push(task)
sess.PendingTasksMutex.Unlock()
return fmt.Errorf("pending")
}
func (s *ISCSITargetDriver) iscsiExecTask(task *iscsiTask) error {
cmd := task.cmd
switch cmd.OpCode {
case OpSCSICmd, OpSCSIOut:
task.state = taskSCSI
// add scsi target process queue
err := s.SCSI.AddCommandQueue(task.conn.session.Target.SCSITarget.TID, task.scmd)
if err != nil {
task.state = 0
}
return err
case OpLogoutReq:
case OpNoopOut:
// just do it in iscsi layer
case OpSCSITaskReq:
sess := task.conn.session
switch cmd.TaskFunc {
case ISCSI_TM_FUNC_ABORT_TASK:
sess.PendingTasksMutex.Lock()
stask := sess.PendingTasks.RemoveByTag(cmd.ReferencedTaskTag)
sess.PendingTasksMutex.Unlock()
if stask == nil {
task.result = ISCSI_TMF_RSP_NO_TASK
} else {
// abort this task
log.Debugf("abort the task[%v]", stask.tag)
if stask.scmd == nil {
stask.scmd = &api.SCSICommand{Result: api.SAM_STAT_TASK_ABORTED}
}
stask.conn = task.conn
log.Debugf("stask.conn: %#v", stask.conn)
stask.conn.buildRespPackage(OpSCSIResp, stask)
stask.conn.rxTask = nil
s.handler(DATAOUT, stask.conn)
task.result = ISCSI_TMF_RSP_COMPLETE
}
case ISCSI_TM_FUNC_ABORT_TASK_SET:
case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
case ISCSI_TM_FUNC_CLEAR_ACA:
fallthrough
case ISCSI_TM_FUNC_CLEAR_TASK_SET:
fallthrough
case ISCSI_TM_FUNC_TARGET_WARM_RESET, ISCSI_TM_FUNC_TARGET_COLD_RESET, ISCSI_TM_FUNC_TASK_REASSIGN:
task.result = ISCSI_TMF_RSP_NOT_SUPPORTED
default:
task.result = ISCSI_TMF_RSP_REJECTED
}
// return response to initiator
return task.conn.buildRespPackage(OpSCSITaskResp, task)
}
return nil
}
// Async Event types per RFC 7143
const (
ASYNC_EVENT_SCSI = 0 // SCSI Asynchronous Event
ASYNC_EVENT_STATUS = 1 // iSCSI Status Update
ASYNC_EVENT_LOGOUT = 2 // iSCSI Logout Request
ASYNC_EVENT_DROP_CONN = 3 // iSCSI Drop Connection
ASYNC_EVENT_DROP_SESS = 4 // iSCSI Drop All Connections
ASYNC_EVENT_NOP = 5 // iSCSI NOP
ASYNC_EVENT_VENDOR = 255 // Vendor Specific Event
)
/*
* SendAsyncMessage sends an asynchronous message to the initiator
* This implements RFC 7143 section 11.10 Asynchronous Message
*/
func (s *ISCSITargetDriver) SendAsyncMessage(conn *iscsiConnection, eventType byte, lun [8]uint8, param1, param2 uint32, data []byte) error {
if conn == nil || conn.state != CONN_STATE_SCSI {
return fmt.Errorf("connection not ready for async message")
}
conn.statSN += 1
conn.resp = &ISCSICommand{
OpCode: OpAsync,
SCSIOpCode: eventType,
Final: true,
LUN: lun,
StatSN: conn.statSN,
ExpCmdSN: conn.expCmdSN,
RawData: data,
}
if conn.session != nil {
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
}
// Parameter1 and Parameter2 are encoded in RawData or could be stored in ISCSICommand
// For simplicity, we encode them at the start of RawData if not already present
if len(data) == 0 && (param1 != 0 || param2 != 0) {
conn.resp.RawData = make([]byte, 8)
copy(conn.resp.RawData[0:4], util.MarshalUint32(param1))
copy(conn.resp.RawData[4:8], util.MarshalUint32(param2))
}
log.Debugf("Sending Async message type=%d to initiator", eventType)
s.handler(DATAOUT, conn)
return nil
}
// SendSCSIAsyncEvent sends a SCSI asynchronous event (e.g., LUN reset, storage change)
func (s *ISCSITargetDriver) SendSCSIAsyncEvent(conn *iscsiConnection, lun [8]uint8, eventCode byte) error {
// SCSI Async Event data format:
// bytes 0-1: Event Code
// bytes 2-3: Reserved
// bytes 4+: Event-specific data
data := []byte{eventCode, 0, 0, 0}
return s.SendAsyncMessage(conn, ASYNC_EVENT_SCSI, lun, 0, 0, data)
}
func (s *ISCSITargetDriver) Stats() scsi.Stats {
s.mu.RLock()
stats := s.TargetStats
stats.SCSIIOCount = map[int]int64{}
for key, value := range s.TargetStats.SCSIIOCount {
stats.SCSIIOCount[key] = value
}
s.mu.RUnlock()
return stats
}
func (s *ISCSITargetDriver) UpdateStats(conn *iscsiConnection) {
s.mu.Lock()
s.TargetStats.IsClientConnected = s.isClientConnected
switch api.SCSICommandType(conn.resp.SCSIOpCode) {
case api.READ_6, api.READ_10, api.READ_12, api.READ_16:
s.TargetStats.ReadIOPS += 1
s.TargetStats.TotalReadTime += int64(time.Since(conn.resp.StartTime))
s.TargetStats.TotalReadBlockCount += int64(conn.resp.ExpectedDataLen)
break
case api.WRITE_6, api.WRITE_10, api.WRITE_12, api.WRITE_16:
s.TargetStats.WriteIOPS += 1
s.TargetStats.TotalWriteTime += int64(time.Since(conn.resp.StartTime))
s.TargetStats.TotalWriteBlockCount += int64(conn.resp.ExpectedDataLen)
break
}
s.TargetStats.SCSIIOCount[(int)(conn.resp.SCSIOpCode)] += 1
s.mu.Unlock()
}