830 lines
21 KiB
Go
830 lines
21 KiB
Go
/*
|
|
Copyright 2016 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 (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/golang/glog"
|
|
"github.com/gostor/gotgt/pkg/api"
|
|
"github.com/gostor/gotgt/pkg/config"
|
|
"github.com/gostor/gotgt/pkg/port"
|
|
"github.com/gostor/gotgt/pkg/scsi"
|
|
"github.com/gostor/gotgt/pkg/util"
|
|
)
|
|
|
|
type ISCSITargetService struct {
|
|
SCSI *scsi.SCSITargetService
|
|
Name string
|
|
iSCSITargets map[string]*ISCSITarget
|
|
}
|
|
|
|
func init() {
|
|
port.RegisterTargetService("iscsi", NewISCSITargetService)
|
|
}
|
|
|
|
func NewISCSITargetService(base *scsi.SCSITargetService) (port.SCSITargetService, error) {
|
|
return &ISCSITargetService{
|
|
Name: "iscsi",
|
|
iSCSITargets: map[string]*ISCSITarget{},
|
|
SCSI: base,
|
|
}, nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) NewTarget(tgtName string, configInfo *config.Config) (port.SCSITargetDriver, error) {
|
|
if _, ok := s.iSCSITargets[tgtName]; ok {
|
|
return nil, fmt.Errorf("target name has been existed")
|
|
}
|
|
stgt, err := s.SCSI.NewSCSITarget(len(s.iSCSITargets), "iscsi", tgtName)
|
|
if err != nil {
|
|
return nil, 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{uint16(tpgtNumber), make(map[string]struct{})}
|
|
targetPortName := fmt.Sprintf("%s,t,0x%02x", tgtName, tpgtNumber)
|
|
scsiTPG.TargetPortGroup = append(scsiTPG.TargetPortGroup, &api.SCSITargetPort{uint16(tpgtNumber), targetPortName})
|
|
for _, portalID := range portalIDArrary {
|
|
portal := configInfo.ISCSIPortals[portalID]
|
|
s.AddiSCSIPortal(tgtName, uint16(tpgtNumber), portal.Portal)
|
|
}
|
|
}
|
|
return tgt, nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) AddiSCSIPortal(tgtName string, tpgt uint16, portal string) error {
|
|
var (
|
|
ok bool
|
|
errMsg string
|
|
target *ISCSITarget
|
|
tpgtInfo *iSCSITPGT
|
|
)
|
|
|
|
if target, ok = s.iSCSITargets[tgtName]; !ok {
|
|
errMsg = fmt.Sprintf("no target %s", tgtName)
|
|
return errors.New(errMsg)
|
|
}
|
|
|
|
if tpgtInfo, ok = target.TPGTs[tpgt]; !ok {
|
|
errMsg = fmt.Sprintf("no tpgt %d", tpgt)
|
|
return errors.New(errMsg)
|
|
}
|
|
tgtPortals := tpgtInfo.Portals
|
|
|
|
if _, ok = tgtPortals[portal]; !ok {
|
|
tgtPortals[portal] = struct{}{}
|
|
} else {
|
|
errMsg := fmt.Sprintf("duplicate portal %s,in %s,%d", portal, tgtName, tpgt)
|
|
return errors.New(errMsg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) 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 *ISCSITargetService) Run() error {
|
|
l, err := net.Listen("tcp", ":3260")
|
|
if err != nil {
|
|
glog.Error(err)
|
|
os.Exit(1)
|
|
}
|
|
defer l.Close()
|
|
|
|
for {
|
|
glog.Info("Listening ...")
|
|
conn, err := l.Accept()
|
|
if err != nil {
|
|
glog.Error(err)
|
|
continue
|
|
}
|
|
glog.Info(conn.LocalAddr().String())
|
|
glog.Info("Accepting ...")
|
|
iscsiConn := &iscsiConnection{conn: conn}
|
|
iscsiConn.init()
|
|
iscsiConn.rxIOState = IOSTATE_RX_BHS
|
|
|
|
glog.Infof("connection is connected from %s...\n", conn.RemoteAddr().String())
|
|
// start a new thread to do with this command
|
|
go s.handler(DATAIN, iscsiConn)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) handler(events byte, conn *iscsiConnection) {
|
|
|
|
if events&DATAIN != 0 {
|
|
glog.V(1).Infof("rx handler processing...")
|
|
go s.rxHandler(conn)
|
|
}
|
|
if conn.state != CONN_STATE_CLOSE && events&DATAOUT != 0 {
|
|
glog.V(1).Infof("tx handler processing...")
|
|
s.txHandler(conn)
|
|
}
|
|
if conn.state == CONN_STATE_CLOSE {
|
|
glog.Warningf("iscsi connection[%d] closed", conn.cid)
|
|
conn.close()
|
|
}
|
|
}
|
|
|
|
func (s *ISCSITargetService) rxHandler(conn *iscsiConnection) {
|
|
var (
|
|
hdigest uint = 0
|
|
ddigest uint = 0
|
|
final bool = false
|
|
cmd *ISCSICommand
|
|
)
|
|
conn.readLock.Lock()
|
|
defer conn.readLock.Unlock()
|
|
if conn.state == CONN_STATE_SCSI {
|
|
hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C
|
|
ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C
|
|
}
|
|
for {
|
|
switch conn.rxIOState {
|
|
case IOSTATE_RX_BHS:
|
|
glog.Infof("rx handler: IOSTATE_RX_BHS")
|
|
buf, length, err := conn.readData(BHS_SIZE)
|
|
if err != nil {
|
|
glog.Error(err)
|
|
return
|
|
}
|
|
if length == 0 {
|
|
glog.Warningf("set connection to close")
|
|
conn.state = CONN_STATE_CLOSE
|
|
return
|
|
}
|
|
conn.rxBuffer = buf
|
|
cmd, err = parseHeader(buf)
|
|
if err != nil {
|
|
glog.Error(err)
|
|
glog.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
|
|
}
|
|
glog.V(2).Infof("got command: \n%s", cmd.String())
|
|
glog.V(2).Infof("got buffer: %v", buf)
|
|
final = true
|
|
case IOSTATE_RX_INIT_AHS:
|
|
conn.rxIOState = IOSTATE_RX_DATA
|
|
break
|
|
if hdigest != 0 {
|
|
conn.rxIOState = IOSTATE_RX_INIT_HDIGEST
|
|
}
|
|
case IOSTATE_RX_DATA:
|
|
if ddigest != 0 {
|
|
conn.rxIOState = IOSTATE_RX_INIT_DDIGEST
|
|
}
|
|
if cmd == nil {
|
|
return
|
|
}
|
|
dl := ((cmd.DataLen + DataPadding - 1) / DataPadding) * DataPadding
|
|
buf := []byte{}
|
|
length := 0
|
|
for length < dl {
|
|
b, l, err := conn.readData(dl - length)
|
|
if err != nil {
|
|
glog.Error(err)
|
|
return
|
|
}
|
|
length += l
|
|
buf = append(buf, b...)
|
|
}
|
|
if length != dl {
|
|
glog.V(2).Infof("get length is %d, but expected %d", length, dl)
|
|
glog.Warningf("set connection to close")
|
|
conn.state = CONN_STATE_CLOSE
|
|
return
|
|
}
|
|
cmd.RawData = buf[:length]
|
|
conn.rxBuffer = append(conn.rxBuffer, buf...)
|
|
final = true
|
|
glog.Infof("got command: \n%s", cmd.String())
|
|
default:
|
|
glog.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:
|
|
glog.Infof("OpLoginReq")
|
|
if err := s.iscsiExecLogin(conn); err != nil {
|
|
glog.Error(err)
|
|
glog.Warningf("set connection to close")
|
|
conn.state = CONN_STATE_CLOSE
|
|
}
|
|
case OpLogoutReq:
|
|
glog.Infof("OpLogoutReq")
|
|
if err := iscsiExecLogout(conn); err != nil {
|
|
glog.Warningf("set connection to close")
|
|
conn.state = CONN_STATE_CLOSE
|
|
}
|
|
case OpTextReq:
|
|
glog.Infof("OpTextReq")
|
|
if err := s.iscsiExecText(conn); err != nil {
|
|
glog.Warningf("set connection to close")
|
|
conn.state = CONN_STATE_CLOSE
|
|
}
|
|
default:
|
|
iscsiExecReject(conn)
|
|
}
|
|
glog.V(2).Infof("connection state is %v", conn.state)
|
|
glog.V(2).Infof("%#v", conn.resp.String())
|
|
s.handler(DATAOUT, conn)
|
|
}
|
|
}
|
|
|
|
func (s *ISCSITargetService) iscsiExecLogin(conn *iscsiConnection) error {
|
|
var (
|
|
target *ISCSITarget
|
|
cmd = conn.req
|
|
TPGT uint16
|
|
err error
|
|
)
|
|
conn.resp = &ISCSICommand{
|
|
OpCode: OpLoginResp,
|
|
Transit: true,
|
|
CSG: cmd.CSG,
|
|
NSG: FullFeaturePhase,
|
|
StatSN: cmd.ExpStatSN,
|
|
TaskTag: cmd.TaskTag,
|
|
ExpCmdSN: cmd.CmdSN,
|
|
MaxCmdSN: cmd.CmdSN,
|
|
RawData: util.MarshalKVText([]util.KeyValue{
|
|
{"HeaderDigest", "None"},
|
|
{"DataDigest", "None"},
|
|
{"ImmediateData", "Yes"},
|
|
{"InitialR2T", "Yes"},
|
|
{"MaxBurstLength", "262144"},
|
|
{"FirstBurstLength", "65536"},
|
|
{"DefaultTime2Wait", "2"},
|
|
{"DefaultTime2Retain", "0"},
|
|
{"MaxOutstandingR2T", "1"},
|
|
{"IFMarker", "No"},
|
|
{"OFMarker", "No"},
|
|
{"DataPDUInOrder", "Yes"},
|
|
{"DataSequenceInOrder", "Yes"},
|
|
}),
|
|
}
|
|
pairs := util.ParseKVText(cmd.RawData)
|
|
if initiatorName, ok := pairs["InitiatorName"]; ok {
|
|
conn.initiator = initiatorName
|
|
}
|
|
if alias, ok := pairs["InitiatorAlias"]; ok {
|
|
conn.initiatorAlias = alias
|
|
}
|
|
targetName := pairs["TargetName"]
|
|
if sessType, ok := pairs["SessionType"]; ok {
|
|
if sessType == "Normal" {
|
|
conn.sessionType = SESSION_NORMAL
|
|
} else {
|
|
conn.sessionType = SESSION_DISCOVERY
|
|
}
|
|
}
|
|
if conn.sessionType == SESSION_DISCOVERY {
|
|
conn.tid = 0xffff
|
|
} else {
|
|
for _, t := range s.iSCSITargets {
|
|
if t.SCSITarget.Name == targetName {
|
|
target = t
|
|
break
|
|
}
|
|
}
|
|
if target == nil {
|
|
conn.state = CONN_STATE_EXIT
|
|
return fmt.Errorf("No target found with name(%s)", targetName)
|
|
}
|
|
|
|
TPGT, err = target.FindTPG(conn.conn.LocalAddr().String())
|
|
if err != nil {
|
|
conn.state = CONN_STATE_EXIT
|
|
return err
|
|
}
|
|
conn.tpgt = TPGT
|
|
conn.tid = target.TID
|
|
|
|
}
|
|
switch conn.state {
|
|
case CONN_STATE_FREE:
|
|
conn.state = CONN_STATE_SECURITY
|
|
case CONN_STATE_SECURITY:
|
|
}
|
|
|
|
conn.state = CONN_STATE_LOGIN_FULL
|
|
conn.expCmdSN = cmd.CmdSN
|
|
conn.statSN += 1
|
|
|
|
switch conn.sessionType {
|
|
case SESSION_NORMAL:
|
|
if conn.session == nil {
|
|
// create a new session
|
|
sess, err := s.NewISCSISession(conn)
|
|
if err != nil {
|
|
glog.Error(err)
|
|
return err
|
|
}
|
|
conn.session = sess
|
|
}
|
|
case SESSION_DISCOVERY:
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func iscsiExecLogout(conn *iscsiConnection) error {
|
|
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 + 10
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) 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 {
|
|
glog.V(2).Infof("iscsi target: %v", name)
|
|
//glog.V(2).Infof("iscsi target portals: %v", tgt.Portals)
|
|
|
|
result = append(result, util.KeyValue{"TargetName", name})
|
|
for _, tpgt := range tgt.TPGTs {
|
|
for portal := range tpgt.Portals {
|
|
targetPort := fmt.Sprintf("%s,%d", portal, tpgt.TPGT)
|
|
result = append(result, util.KeyValue{"TargetAddress", 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 {
|
|
cmd := conn.req
|
|
conn.resp = &ISCSICommand{
|
|
OpCode: OpNoopIn,
|
|
Final: true,
|
|
NSG: FullFeaturePhase,
|
|
StatSN: cmd.ExpStatSN,
|
|
TaskTag: cmd.TaskTag,
|
|
ExpCmdSN: cmd.CmdSN + 1,
|
|
MaxCmdSN: cmd.CmdSN + 10,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func iscsiExecTMFunction(conn *iscsiConnection) error {
|
|
cmd := conn.req
|
|
conn.resp = &ISCSICommand{
|
|
OpCode: OpSCSITaskResp,
|
|
Final: true,
|
|
NSG: FullFeaturePhase,
|
|
StatSN: cmd.ExpStatSN,
|
|
TaskTag: cmd.TaskTag,
|
|
ExpCmdSN: cmd.CmdSN + 1,
|
|
MaxCmdSN: cmd.CmdSN + 10,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func iscsiExecReject(conn *iscsiConnection) error {
|
|
conn.resp = &ISCSICommand{
|
|
OpCode: OpReject,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func iscsiExecR2T(conn *iscsiConnection) error {
|
|
conn.session.ExpCmdSN += 1
|
|
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}}
|
|
conn.txIOState = IOSTATE_TX_BHS
|
|
conn.statSN += 1
|
|
task := conn.rxTask
|
|
resp := &ISCSICommand{
|
|
OpCode: OpReady,
|
|
Immediate: true,
|
|
Final: true,
|
|
StatSN: conn.req.ExpStatSN,
|
|
TaskTag: conn.req.TaskTag,
|
|
ExpCmdSN: conn.session.ExpCmdSN,
|
|
MaxCmdSN: conn.session.ExpCmdSN + 10,
|
|
R2TSN: task.r2tSN,
|
|
BufferOffset: uint32(task.offset),
|
|
DesiredLength: uint32(task.r2tCount),
|
|
}
|
|
if val := sessionKeys[ISCSI_PARAM_MAX_BURST].def; task.r2tCount > int(val) {
|
|
resp.DesiredLength = uint32(val)
|
|
}
|
|
conn.resp = resp
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) txHandler(conn *iscsiConnection) {
|
|
var (
|
|
hdigest uint = 0
|
|
ddigest uint = 0
|
|
final bool = false
|
|
)
|
|
if conn.state == CONN_STATE_SCSI {
|
|
hdigest = conn.sessionParam[ISCSI_PARAM_HDRDGST_EN].Value & DIGEST_CRC32C
|
|
ddigest = conn.sessionParam[ISCSI_PARAM_DATADGST_EN].Value & DIGEST_CRC32C
|
|
}
|
|
if conn.state == CONN_STATE_SCSI && conn.txTask == nil {
|
|
err := s.scsiCommandHandler(conn)
|
|
if err != nil {
|
|
glog.Error(err)
|
|
return
|
|
}
|
|
}
|
|
for {
|
|
switch conn.txIOState {
|
|
case IOSTATE_TX_BHS:
|
|
glog.V(2).Infof("ready to write response")
|
|
glog.V(2).Infof("%s", conn.resp.String())
|
|
glog.V(2).Infof("length of RawData is %d", len(conn.resp.RawData))
|
|
glog.V(2).Infof("length of resp is %d", len(conn.resp.Bytes()))
|
|
if l, err := conn.write(conn.resp.Bytes()); err != nil {
|
|
glog.Error(err)
|
|
return
|
|
} else {
|
|
conn.txIOState = IOSTATE_TX_INIT_AHS
|
|
glog.V(2).Infof("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:
|
|
glog.Errorf("error %d %d\n", conn.state, conn.txIOState)
|
|
return
|
|
}
|
|
|
|
if final {
|
|
break
|
|
}
|
|
}
|
|
|
|
glog.V(3).Infof("connection state: %d", conn.state)
|
|
switch conn.state {
|
|
case CONN_STATE_CLOSE, CONN_STATE_EXIT:
|
|
glog.Warningf("set connection to close")
|
|
conn.state = CONN_STATE_CLOSE
|
|
case CONN_STATE_SECURITY_LOGIN:
|
|
conn.state = CONN_STATE_LOGIN
|
|
glog.V(3).Infof("CONN_STATE_LOGIN")
|
|
case CONN_STATE_SECURITY_FULL, CONN_STATE_LOGIN_FULL:
|
|
if conn.sessionType == SESSION_NORMAL {
|
|
conn.state = CONN_STATE_KERNEL
|
|
glog.Infof("CONN_STATE_KERNEL")
|
|
conn.state = CONN_STATE_SCSI
|
|
glog.V(3).Infof("CONN_STATE_SCSI")
|
|
} else {
|
|
conn.state = CONN_STATE_FULL
|
|
glog.V(3).Infof("CONN_STATE_FULL")
|
|
}
|
|
conn.rxIOState = IOSTATE_RX_BHS
|
|
s.handler(DATAIN, conn)
|
|
case CONN_STATE_SCSI:
|
|
conn.txTask = nil
|
|
default:
|
|
glog.Warningf("unexpected connection state: %d", conn.state)
|
|
conn.rxIOState = IOSTATE_RX_BHS
|
|
s.handler(DATAIN, conn)
|
|
}
|
|
glog.Infof("%d", conn.state)
|
|
}
|
|
|
|
func (s *ISCSITargetService) scsiCommandHandler(conn *iscsiConnection) (err error) {
|
|
req := conn.req
|
|
switch req.OpCode {
|
|
case OpSCSICmd:
|
|
glog.V(2).Infof("SCSI Command processing...")
|
|
scmd := &api.SCSICommand{}
|
|
task := &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: scmd}
|
|
if req.Write {
|
|
task.offset = req.DataLen
|
|
task.r2tCount = int(req.ExpectedDataLen) - req.DataLen
|
|
if !req.Final {
|
|
task.unsolCount = 1
|
|
}
|
|
glog.V(2).Infof("SCSI write, R2T count: %d, unsol Count: %d, offset: %d", task.r2tCount, task.unsolCount, task.offset)
|
|
|
|
if task.scmd.OutSDBBuffer.Buffer == nil {
|
|
task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer([]byte{})
|
|
}
|
|
task.scmd.OutSDBBuffer.Buffer.Write(conn.req.RawData)
|
|
if task.r2tCount > 0 {
|
|
// prepare to receive more data
|
|
task.state = taskPending
|
|
conn.session.PendingTasks.Push(task)
|
|
conn.rxTask = task
|
|
iscsiExecR2T(conn)
|
|
break
|
|
}
|
|
}
|
|
task.offset = 0
|
|
conn.rxTask = task
|
|
if err = s.iscsiTaskQueueHandler(task); err != nil {
|
|
return
|
|
} else {
|
|
conn.rxTask = nil
|
|
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}}
|
|
conn.txIOState = IOSTATE_TX_BHS
|
|
conn.statSN += 1
|
|
resp := &ISCSICommand{
|
|
Immediate: true,
|
|
Final: true,
|
|
StatSN: req.ExpStatSN,
|
|
TaskTag: req.TaskTag,
|
|
ExpCmdSN: conn.session.ExpCmdSN,
|
|
MaxCmdSN: conn.session.ExpCmdSN + 10,
|
|
Status: scmd.Result,
|
|
SCSIResponse: 0x00,
|
|
HasStatus: true,
|
|
}
|
|
switch scmd.Direction {
|
|
case api.SCSIDataRead:
|
|
resp.OpCode = OpSCSIIn
|
|
if scmd.InSDBBuffer.Buffer != nil {
|
|
buf := scmd.InSDBBuffer.Buffer.Bytes()
|
|
resp.RawData = buf
|
|
} else {
|
|
resp.RawData = []byte{}
|
|
}
|
|
case api.SCSIDataWrite:
|
|
resp.OpCode = OpSCSIResp
|
|
if scmd.InSDBBuffer.Buffer != nil {
|
|
buf := scmd.InSDBBuffer.Buffer.Bytes()
|
|
resp.RawData = buf
|
|
} else {
|
|
resp.RawData = []byte{}
|
|
}
|
|
case api.SCSIDataBidirection:
|
|
case api.SCSIDataNone:
|
|
resp.OpCode = OpSCSIResp
|
|
}
|
|
if scmd.Result != 0 && scmd.SenseBuffer != nil {
|
|
resp.RawData = scmd.SenseBuffer.Bytes()
|
|
}
|
|
conn.resp = resp
|
|
}
|
|
case OpSCSITaskReq:
|
|
// task management function
|
|
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, state: taskPending}
|
|
conn.txIOState = IOSTATE_TX_BHS
|
|
iscsiExecTMFunction(conn)
|
|
case OpSCSIOut:
|
|
glog.V(1).Infof("iSCSI Data-out processing...")
|
|
var task *iscsiTask
|
|
for _, t := range conn.session.PendingTasks {
|
|
if t.tag == conn.req.TaskTag {
|
|
task = t
|
|
}
|
|
}
|
|
if task == nil {
|
|
err = fmt.Errorf("Cannot find iSCSI task with tag[%v]", conn.req.TaskTag)
|
|
glog.Error(err)
|
|
return
|
|
}
|
|
task.offset = task.offset + conn.req.DataLen
|
|
task.r2tCount = task.r2tCount - conn.req.DataLen
|
|
task.scmd.OutSDBBuffer.Buffer.Write(conn.req.RawData)
|
|
glog.V(2).Infof("Final: %v", conn.req.Final)
|
|
glog.V(2).Infof("r2tCount: %v", task.r2tCount)
|
|
if !conn.req.Final {
|
|
glog.V(1).Infof("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
|
|
task.r2tSN += 1
|
|
conn.rxTask = task
|
|
iscsiExecR2T(conn)
|
|
break
|
|
}
|
|
task.offset = 0
|
|
glog.V(1).Infof("Process the Data-out package")
|
|
conn.rxTask = task
|
|
if err = s.iscsiExecTask(task); err != nil {
|
|
return
|
|
} else {
|
|
conn.rxTask = nil
|
|
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag, scmd: &api.SCSICommand{}, state: taskSCSI}
|
|
conn.txIOState = IOSTATE_TX_BHS
|
|
conn.statSN += 1
|
|
resp := &ISCSICommand{
|
|
OpCode: OpSCSIResp,
|
|
Immediate: true,
|
|
Final: true,
|
|
StatSN: req.ExpStatSN,
|
|
TaskTag: req.TaskTag,
|
|
ExpCmdSN: conn.session.ExpCmdSN,
|
|
MaxCmdSN: conn.session.ExpCmdSN + 10,
|
|
Status: task.scmd.Result,
|
|
SCSIResponse: 0x00,
|
|
HasStatus: true,
|
|
}
|
|
conn.resp = resp
|
|
}
|
|
case OpNoopOut:
|
|
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag}
|
|
conn.txIOState = IOSTATE_TX_BHS
|
|
iscsiExecNoopOut(conn)
|
|
case OpLogoutReq:
|
|
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag}
|
|
conn.txIOState = IOSTATE_TX_BHS
|
|
iscsiExecLogout(conn)
|
|
case OpTextReq, OpSNACKReq:
|
|
err = fmt.Errorf("Cannot handle yet %s", opCodeMap[conn.req.OpCode])
|
|
glog.Error(err)
|
|
return
|
|
default:
|
|
err = fmt.Errorf("Unknown op %s", opCodeMap[conn.req.OpCode])
|
|
glog.Error(err)
|
|
return
|
|
}
|
|
conn.rxIOState = IOSTATE_RX_BHS
|
|
s.handler(DATAIN|DATAOUT, conn)
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) iscsiTaskQueueHandler(task *iscsiTask) error {
|
|
conn := task.conn
|
|
sess := conn.session
|
|
cmd := task.cmd
|
|
if cmd.Immediate {
|
|
return s.iscsiExecTask(task)
|
|
}
|
|
cmdsn := cmd.CmdSN
|
|
glog.V(2).Infof("CmdSN of command is %d, ExpCmdSN of session is %d", cmdsn, sess.ExpCmdSN)
|
|
if cmdsn == sess.ExpCmdSN {
|
|
retry:
|
|
cmdsn += 1
|
|
sess.ExpCmdSN = cmdsn
|
|
glog.V(2).Infof("session's ExpCmdSN is %d", cmdsn)
|
|
|
|
glog.V(2).Infof("process task(%d)", task.cmd.CmdSN)
|
|
if err := s.iscsiExecTask(task); err != nil {
|
|
glog.Error(err)
|
|
}
|
|
if len(sess.PendingTasks) == 0 {
|
|
return nil
|
|
}
|
|
task = sess.PendingTasks.Pop().(*iscsiTask)
|
|
cmd = task.cmd
|
|
if cmd.CmdSN != cmdsn {
|
|
sess.PendingTasks.Push(task)
|
|
return nil
|
|
}
|
|
task.state = taskSCSI
|
|
goto retry
|
|
} else {
|
|
if cmd.CmdSN < sess.ExpCmdSN {
|
|
err := fmt.Errorf("unexpected cmd serial number: (%d, %d)", cmd.CmdSN, sess.ExpCmdSN)
|
|
glog.Error(err)
|
|
return err
|
|
}
|
|
glog.V(1).Infof("add task(%d) into task queue", task.cmd.CmdSN)
|
|
// add this connection into queue and set this task as pending task
|
|
task.state = taskPending
|
|
sess.PendingTasks.Push(task)
|
|
return fmt.Errorf("pending")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetService) iscsiExecTask(task *iscsiTask) error {
|
|
cmd := task.cmd
|
|
switch cmd.OpCode {
|
|
case OpSCSICmd, OpSCSIOut:
|
|
if cmd.Read {
|
|
if cmd.Write {
|
|
task.scmd.Direction = api.SCSIDataBidirection
|
|
} else {
|
|
task.scmd.Direction = api.SCSIDataRead
|
|
}
|
|
} else {
|
|
if cmd.Write {
|
|
task.scmd.Direction = api.SCSIDataWrite
|
|
}
|
|
}
|
|
task.scmd.CommandITNID = task.conn.session.Tsih
|
|
task.scmd.SCB = bytes.NewBuffer(cmd.CDB)
|
|
task.scmd.SCBLength = len(cmd.CDB)
|
|
task.scmd.Lun = cmd.LUN
|
|
task.scmd.Tag = uint64(cmd.TaskTag)
|
|
task.scmd.RelTargetPortID = task.conn.tpgt
|
|
task.state = taskSCSI
|
|
if task.scmd.OutSDBBuffer.Buffer == nil {
|
|
task.scmd.OutSDBBuffer.Buffer = bytes.NewBuffer(cmd.RawData)
|
|
}
|
|
// 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
|
|
}
|
|
return nil
|
|
}
|