-
Notifications
You must be signed in to change notification settings - Fork 21
Add more info to iscsi dump info #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,12 @@ | |
""" | ||
import argparse | ||
from typing import Iterator | ||
from typing import Dict | ||
from typing import Any | ||
|
||
import drgn | ||
import socket | ||
from drgn import cast | ||
|
||
from drgn import Object | ||
from drgn import Program | ||
|
@@ -21,8 +27,83 @@ | |
from drgn_tools.table import print_dictionary | ||
from drgn_tools.util import enum_name_get | ||
|
||
ISCSI_SESSION_STATES = ["LOGGED_IN", "FAILED", "FREE"] | ||
# Available Hardware and Software iSCSI Transports | ||
iscsi_transports = ["bnx2i", | ||
"cxgb3i", | ||
"cxgb4i", | ||
"iser", | ||
"qedi", | ||
"bfin dpmc", | ||
"h1940-bt", | ||
"tcp"] | ||
|
||
# iSCSI Session States | ||
iscsi_session_states = ["UNKNOWN", | ||
"FREE", | ||
"LOGGED_IN", | ||
"FAILED", | ||
"TERMINATE", | ||
"IN_RECOVERY", | ||
"RECOVERY_FAILED", | ||
"LOGGING_OUT" | ||
] | ||
Comment on lines
+41
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First: please make all global variables named in Second: These states appear to be an enumeration from from typing import Dict, Type
from drgn import Program
from drgn.helpers.common import enum_type_to_class
def iscsi_constants(prog: Program) -> Dict[str, Type]:
return {
"ISCSI_STATE": enum_type_to_class(
prog.constant("ISCSI_STATE_FREE").type_,
"IscsiState",
prefix="ISCSI_STATE",
),
...
} You can do this for each enum: choose the most common variant (the one whose name is least likely to change), and then create the Python class with enum_type_to_class(). Once you have the python enum class corresponding to it, it will be quite easy to just lookup the name. For instance: state = 5 # in reality, this would come from the program
consts = iscsi_constants(prog)
value = consts(state)
print(str(value)) That goes for all these lists of constants below - look them up from the program and create them dynamically. That way if any constants change in any kernel version, you'll get the right values. |
||
|
||
# iSCSI Connection Stages | ||
iscsi_connection_stages = ["INITIAL_STAGE", | ||
"STARTED", | ||
"STOPPED", | ||
"CLEANUP_WAIT" | ||
] | ||
# iSCSI Connection States | ||
iscsi_connection_states = ["UP", | ||
"DOWN", | ||
"FAILED", | ||
"BOUND" | ||
] | ||
|
||
# SCSI device States | ||
scsi_device_states = ["unknown", | ||
"created", | ||
"running", | ||
"cancel", | ||
"del", | ||
"quiesce", | ||
"offline", | ||
"transport offline", | ||
"blocked", | ||
"created block" | ||
] | ||
|
||
# Scsi_Host states | ||
scsi_host_states = ["UNKNOWN", | ||
"CREATED", | ||
"RUNNING", | ||
"CANCEL", | ||
"DEL", | ||
"RECOVERY", | ||
"CANCEL_RECOVERY", | ||
"DEL_RECOVERY" | ||
] | ||
# Sub-Headings | ||
sub_headings = ["Interface", | ||
"CHAP", | ||
"Timeouts", | ||
"Negotiated iSCSI params", | ||
"Attached SCSI devices" | ||
] | ||
|
||
def print_iscsi_info(info: Dict[str, Any]) -> None: | ||
for line in info: | ||
if "Target IQN" in line: | ||
print(f"{line}: {info[line]}") | ||
elif line == "Current Portal" or line == "Persistent Portal": | ||
print(f"\t{line}: {info[line]}") | ||
elif any(line in s for s in sub_headings): | ||
print(f"\t\t"+"*" * (len(line) + 1)) | ||
print(f"\t\t{line}: {info[line]}") | ||
print(f"\t\t"+"*" * (len(line) + 1)) | ||
else: | ||
print(f"\t\t{line}: {info[line]}") | ||
Comment on lines
+88
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do like seeing a separation between helpers that get information, and those that print information. That makes it easier for others to write new code based on your helpers. So I do like what you're trying to do here. But I'm not certain that it's really helping to have a
I think there are two ways to go about this. Either change For an example of the latter, see |
||
|
||
def for_each_iscsi_host(prog: Program) -> Iterator[Object]: | ||
""" | ||
|
@@ -62,8 +143,95 @@ def for_each_iscsi_session(prog: Program) -> Iterator[Object]: | |
address=cls_session.dd_data, | ||
) | ||
|
||
def get_iscsi_info(iscsi_conn: Object, prog: Program ) -> Dict[str, Any]: | ||
|
||
def print_iscsi_sessions(prog: Program) -> None: | ||
iscsi_session = iscsi_conn.session | ||
iscsi_cls_conn = iscsi_conn.cls_conn | ||
iscsi_cls_session = iscsi_session.cls_session | ||
iscsi_tcp_conn = cast("struct iscsi_tcp_conn *", iscsi_conn.dd_data) | ||
iscsi_sw_tcp_conn = cast("struct iscsi_sw_tcp_conn *", iscsi_tcp_conn.dd_data) | ||
iscsi_socket = iscsi_sw_tcp_conn.sock | ||
iscsi_sock = iscsi_socket.sk | ||
scsi_host = iscsi_session.host | ||
iscsi_host_data = scsi_host.hostdata | ||
iscsi_host = cast("struct iscsi_host *", iscsi_host_data) | ||
|
||
try: | ||
if iscsi_sock.__sk_common.skc_family == 2: | ||
current_address = iscsi_sock.__sk_common.skc_daddr | ||
current_address = prog.read_u32(current_address.address_of_()) | ||
current_ip = socket.inet_ntoa(bytes([(current_address) & 0xFF, | ||
(current_address >> 8) & 0xFF, | ||
(current_address >> 16) & 0xFF, | ||
(current_address >> 24) & 0xFF])) | ||
current_port = iscsi_sock.__sk_common.skc_dport | ||
current_port = prog.read_u16(current_port.address_of_()) | ||
current_port = socket.ntohs(current_port) | ||
local_address = iscsi_sock.__sk_common.skc_rcv_saddr | ||
local_address = prog.read_u32(local_address.address_of_()) | ||
local_ip = socket.inet_ntoa(bytes([(local_address) & 0xFF, | ||
(local_address >> 8) & 0xFF, | ||
(local_address >> 16) & 0xFF, | ||
(local_address >> 24) & 0xFF])) | ||
Comment on lines
+161
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For IP addresses, you should use For the port, only do the byte swap if the program is little endian. (which is true for all the architectures we use, but we really should do it correctly). For example, see these lines in the
|
||
except drgn.FaultError: | ||
current_ip = "[default]" | ||
local_ip = "[default]" | ||
current_port = "" | ||
return { | ||
"Target IQN":escape_ascii_string(iscsi_session.targetname.string_()), | ||
"Current Portal":(current_ip+":"+str(current_port)), | ||
"Persistent Portal": escape_ascii_string(iscsi_conn.persistent_address.string_())+ | ||
":"+str(iscsi_conn.persistent_port.value_())+","+ | ||
str(iscsi_session.tpgt.value_()), | ||
"Interface":"", | ||
"Iface Name":escape_ascii_string(iscsi_session.ifacename.string_()), | ||
"Iface Transport":escape_ascii_string(iscsi_cls_session.transport.name.string_()), | ||
"Iface Initiatorname": escape_ascii_string(iscsi_session.initiatorname.string_()), | ||
"Iface IPaddress": local_ip, | ||
"Iface HWaddress": escape_ascii_string(iscsi_host.hwaddress.string_()) if iscsi_host.hwaddress else "default", | ||
"Iface Netdev": escape_ascii_string(iscsi_host.netdev.string_()) if iscsi_host.netdev else "default", | ||
"SID": iscsi_cls_session.sid.value_(), | ||
"iSCSI Connection State": iscsi_connection_states[iscsi_cls_conn.state.value_()], | ||
"iSCSI Session State": iscsi_session_states[iscsi_session.state.value_()], | ||
"Internal iscsid Session State": iscsi_session_states[iscsi_session.state.value_()], | ||
"Timeouts":"", | ||
"Recovery Timeout": iscsi_cls_session.recovery_tmo.value_(), | ||
"Target Reset Timeout": iscsi_session.tgt_reset_timeout.value_(), | ||
"LUN Reset Timeout": iscsi_session.lu_reset_timeout.value_(), | ||
"Abort Timeout": iscsi_session.abort_timeout.value_(), | ||
"CHAP":"", | ||
"username": (escape_ascii_string(iscsi_session.username.string_()) if iscsi_session.username else "<empty>"), | ||
"password": (escape_ascii_string(iscsi_session.password.string_()) if iscsi_session.password else "********"), | ||
"username_in": (escape_ascii_string(iscsi_session.username_in.string_()) if iscsi_session.username_in else "<empty>"), | ||
"password_in": (escape_ascii_string(iscsi_session.password_in.string_()) if iscsi_session.password_in else "********"), | ||
Comment on lines
+203
to
+206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, what's the security implication of this? We should not print and include username/password combinations in corelens reports just because they are there. Do we have a valid debugging use case for including them? I would strongly discourage this unless absolutely necessary. |
||
"Negotiated iSCSI params":"", | ||
"HeaderDigest": (iscsi_conn.hdrdgst_en.value_() if iscsi_conn.hdrdgst_en else "None"), | ||
"DataDigest": (iscsi_conn.datadgst_en.value_() if iscsi_conn.datadgst_en else "None"), | ||
"MaxRecvDataSegmentLength": iscsi_conn.max_recv_dlength.value_(), | ||
"MaxXmitDataSegmentLength": iscsi_conn.max_xmit_dlength.value_(), | ||
"FirstBurstLength": iscsi_session.first_burst.value_(), | ||
"MaxBurstLength": iscsi_session.max_burst.value_(), | ||
"ImmediateData": ("Yes" if iscsi_session.imm_data_en.value_() else "No"), | ||
"InitialR2T": ("Yes" if iscsi_session.initial_r2t_en.value_() else "No"), | ||
"MaxOutstandingR2T": iscsi_session.max_r2t.value_(), | ||
"Attached SCSI devices":"", | ||
f"Host Number: {scsi_host.host_no.value_()} State": scsi_host_states[scsi_host.shost_state.value_()] | ||
} | ||
|
||
def get_iscsi_disks_info(scsi_device: Object, prog: Program) -> Dict[str, Any]: | ||
scsi_host = scsi_device.host | ||
lun = scsi_device.lun.value_() | ||
host_no = scsi_host.host_no.value_() | ||
Id = scsi_device.id.value_() | ||
channel = scsi_device.channel.value_() | ||
sdev_state = scsi_device_states[scsi_device.sdev_state.value_()] | ||
sdev_name = scsi_device_name(prog, scsi_device) | ||
return { | ||
f"scsi{host_no} channel {channel} id {Id} Lun": lun, | ||
f"\tAttached scsi disk {sdev_name}\tState": sdev_state | ||
} | ||
|
||
def dump_iscsi_sessions(prog: Program) -> None: | ||
""" | ||
Dump iscsi sessions. | ||
|
||
|
@@ -73,76 +241,15 @@ def print_iscsi_sessions(prog: Program) -> None: | |
print(msg) | ||
return | ||
|
||
output = {} | ||
|
||
for session in reversed(list(for_each_iscsi_session(prog))): | ||
print("**********") | ||
conn = session.leadconn | ||
|
||
persistent_address = escape_ascii_string( | ||
conn.persistent_address.string_() | ||
) | ||
persistent_port = int(conn.persistent_port) | ||
output[ | ||
"Scsi_Host" | ||
] = f"host{session.host.host_no.value_()} ({hex(session.host.value_())})" | ||
output["Session"] = hex(session.address_of_()) | ||
output["SID"] = str(int(session.cls_session.sid)) | ||
output["Persistent Portal"] = f"{persistent_address}:{persistent_port}" | ||
output["Iface Name"] = escape_ascii_string(session.ifacename.string_()) | ||
output["Session State"] = ISCSI_SESSION_STATES[ | ||
session.cls_session.state | ||
] | ||
connstate = enum_type_to_class( | ||
prog.type("enum iscsi_connection_state"), "connstate" | ||
) | ||
output["Connection State"] = str( | ||
enum_name_get( | ||
connstate, | ||
conn.cls_conn.state, | ||
"UNKNOWN", | ||
) | ||
) | ||
output["Initiatorname"] = escape_ascii_string( | ||
session.initiatorname.string_() | ||
) | ||
output["Targetname"] = escape_ascii_string( | ||
session.targetname.string_() | ||
) | ||
|
||
print_dictionary(output) | ||
|
||
print("Attached SCSI devices: ") | ||
print("**********") | ||
print( | ||
"Host Number: {} STATE: {}".format( | ||
session.host.host_no.value_(), | ||
session.host.shost_state.format_(type_name=False), | ||
) | ||
) | ||
iscsi_info = get_iscsi_info(conn, prog) | ||
print_iscsi_info(iscsi_info) | ||
|
||
for scsi_dev in for_each_scsi_host_device(prog, session.host): | ||
name = scsi_device_name(prog, scsi_dev) | ||
print( | ||
"scsi{} Channel {} Id {} Lun: {}".format( | ||
session.host.host_no.value_(), | ||
int(scsi_dev.channel), | ||
int(scsi_dev.id), | ||
int(scsi_dev.lun), | ||
) | ||
) | ||
sdev_state = enum_type_to_class( | ||
prog.type("enum scsi_device_state"), "sdev_state" | ||
) | ||
state = enum_name_get( | ||
sdev_state, | ||
scsi_dev.sdev_state, | ||
"UNKNOWN", | ||
) | ||
print(" Attached iscsi disk: {} State: {}".format(name, state)) | ||
|
||
print() | ||
|
||
iscsi_disks_info = get_iscsi_disks_info(scsi_dev, prog) | ||
print_iscsi_info(iscsi_disks_info) | ||
Comment on lines
250
to
+252
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be a table instead? It would probably make it easier to read. See the |
||
|
||
class Iscsi(CorelensModule): | ||
""" | ||
|
@@ -153,4 +260,4 @@ class Iscsi(CorelensModule): | |
skip_unless_have_kmod = ["libiscsi", "scsi_transport_iscsi"] | ||
|
||
def run(self, prog: Program, args: argparse.Namespace) -> None: | ||
print_iscsi_sessions(prog) | ||
dump_iscsi_sessions(prog) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,4 @@ | |
|
||
|
||
def test_iscsi(prog): | ||
iscsi.print_iscsi_sessions(prog) | ||
iscsi.dump_iscsi_sessions(prog) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constant is unused