ksmbd — 리눅스 커널 SMB3 서버
Linux 5.15 이후 메인라인에서 제공되는 커널 SMB 서버 ksmbd를 서버 구현 관점으로 해부합니다. SMB2/SMB3 명령 디코딩과 세션/트리/파일 핸들 상태 관리, ksmbd.mountd와의 제어 채널, VFS 연동 시 ACL·xattr·파일 잠금 매핑, oplock/lease 기반 캐시 일관성, 서명·암호화·Kerberos/NTLM 인증 흐름, SMB3 멀티채널, Durable Handle, Compound Request, Change Notify, 워커 스레드풀 아키텍처, Samba 대비 구조적 차이와 배포 시 튜닝/보안 체크포인트를 상세히 정리합니다.
핵심 요약
- ksmbd — Linux 5.15 메인라인에 포함된 커널 공간 SMB3 서버입니다. Windows/macOS/Linux 클라이언트가 SMB 프로토콜로 파일을 공유할 수 있습니다.
- SMB3 프로토콜 — SMB2.0/2.1/3.0/3.1.1을 지원합니다. Samba와 달리 커널 직접 처리로 컨텍스트 전환 오버헤드가 줄어듭니다.
- Netlink IPC — 인증, 사용자 계정 관리 등 보안에 민감한 기능은 Netlink 소켓으로 사용자 공간 ksmbd-tools와 통신합니다.
- VFS 통합 — POSIX ACL, xattr, 파일 잠금(fcntl/flock), oplock/lease를 VFS를 통해 처리합니다.
- oplock/lease — SMB 클라이언트의 파일 캐싱을 제어합니다. 다른 클라이언트가 접근하면 lease break 알림을 보냅니다.
- 암호화/서명 — SMB3 AES-128-CCM/GCM 암호화, HMAC-SHA256/AES-CMAC 서명을 지원합니다.
- Samba와의 차이 — ksmbd는 커널 내장이므로 시스템 콜 오버헤드가 없지만 Samba보다 기능이 적습니다. NAS 용도로는 ksmbd가 간단합니다.
- ksmbd-tools — 사용자 관리(
ksmbd.adduser), 공유 설정(/etc/ksmbd/smb.conf), 데몬(ksmbd.mountd)으로 구성됩니다.
단계별 이해
- ksmbd 활성화 확인 —
lsmod | grep ksmbd로 커널 모듈이 로드되었는지 확인합니다.Linux 5.15 이상에서
CONFIG_SMB_SERVER=m으로 빌드되어 있어야 합니다. - ksmbd-tools 설치 및 설정 —
/etc/ksmbd/smb.conf에 공유 디렉터리를 정의하고ksmbd.adduser로 사용자를 추가합니다.Samba의
smb.conf와 문법이 유사하지만 지원 옵션이 다릅니다. - 커널 아키텍처 이해 — TCP 연결 수신 → SMB 헤더 파싱 → VFS 호출 → 응답 전송의 흐름을 파악합니다.
인증/계정 조회만 Netlink으로 사용자 공간에 위임합니다.
- oplock/lease 동작 이해 — 클라이언트가 파일을 열면 exclusive oplock을 획득합니다. 다른 클라이언트가 같은 파일을 열면 서버가 lease break를 보냅니다.
클라이언트는 캐시를 플러시하고 oplock을 반납한 후 다른 클라이언트가 접근할 수 있습니다.
- SMB3 암호화 적용 —
smb.conf에서encrypt = true를 설정하면 SMB3 AES-GCM으로 모든 데이터가 암호화됩니다.클라이언트에서
seal마운트 옵션과 함께 사용하세요. - 성능 벤치마크 —
smbclient나fio로 처리량을 측정합니다. ksmbd는 Samba 대비 소규모 랜덤 I/O에서 이점이 있습니다.대규모 프로덕션 환경에서는 여전히 Samba가 더 안정적이고 기능이 풍부합니다.
ksmbd 개요
ksmbd는 Linux 커널 5.15(2021년 11월)에 메인라인으로 포함된 커널 공간 SMB3 파일 서버입니다. Samsung에서 개발하여 커뮤니티에 기여했으며, 기존 사용자 공간 Samba와 달리 커널에서 직접 SMB 프로토콜을 처리합니다.
| 특성 | 내용 |
|---|---|
| 메인라인 포함 | Linux 5.15 (2021년 11월) |
| 소스 위치 | fs/ksmbd/ |
| SMB 버전 지원 | SMB2.0, SMB2.1, SMB3.0, SMB3.1.1 |
| 포트 | TCP 445 (SMB over TCP), TCP 139 (NetBIOS — ksmbd-tools 처리) |
| 설정 파일 | /etc/ksmbd/smb.conf |
| 사용자 DB | /etc/ksmbd/ksmbdpwd.db |
| Kconfig | CONFIG_SMB_SERVER |
| 의존 모듈 | CONFIG_CRYPTO, CONFIG_NLATTR, CONFIG_INET |
| RDMA 지원 | CONFIG_SMB_SERVER_SMBDIRECT (SMB Direct over RDMA) |
| 라이선스 | GPLv2 |
ksmbd-tools에 위임합니다. 이는 공격 표면을 줄이면서도 커널의 제로카피 I/O와 페이지 캐시 직접 접근 장점을 누리기 위한 설계입니다.
Kconfig 옵션
# fs/ksmbd/Kconfig
config SMB_SERVER
tristate "SMB3 server support"
depends on INET
depends on MULTIUSER
depends on FILE_LOCKING
select NLS
select NLS_UTF8
select CRYPTO
select CRYPTO_MD5
select CRYPTO_HMAC
select CRYPTO_ECB
select CRYPTO_AES
select CRYPTO_SHA256
select CRYPTO_SHA512
select CRYPTO_CMAC
select CRYPTO_CCM
select CRYPTO_GCM
select ASN1
select OID_REGISTRY
config SMB_SERVER_SMBDIRECT
bool "SMB Direct support (SMB over RDMA)"
depends on SMB_SERVER && INFINIBAND
select SG_POOL
SMB 프로토콜 개요
SMB(Server Message Block)는 Windows 파일 공유 프로토콜로, CIFS라고도 불립니다. SMB2(Vista)부터 크게 개편되었고, SMB3는 암호화와 멀티채널을 추가했습니다.
SMB 버전 비교
| 버전 | 도입 OS | 주요 기능 | ksmbd 지원 |
|---|---|---|---|
| SMB1/CIFS | Windows NT | 기본 파일 공유 (보안 취약) | 미지원 |
| SMB2.0 | Vista/2008 | 파이프라이닝, compound 요청 | 지원 |
| SMB2.1 | Windows 7 | Client Oplock Lease | 지원 |
| SMB3.0 | Windows 8 | 암호화(AES-CCM), 멀티채널, RDMA | 지원 |
| SMB3.0.2 | Windows 8.1 | Durable Handle v2, 개선된 Lease | 지원 |
| SMB3.1.1 | Windows 10 | Pre-auth 무결성, AES-GCM, 보안 강화 | 지원 |
SMB PDU 구조
/* SMB2 헤더 구조 (64바이트) */
struct smb2_hdr {
__le32 ProtocolId; /* 0xFE534D42 "\xFESMB" */
__le16 StructureSize; /* 64 */
__le16 CreditCharge; /* 크레딧 차감량 */
__le32 Status; /* NT 상태 코드 (응답) */
__le16 Command; /* SMB2_NEGOTIATE, SMB2_CREATE 등 */
__le16 CreditRequest; /* 요청/부여하는 크레딧 수 */
__le32 Flags; /* SMB2_FLAGS_SERVER_TO_REDIR 등 */
__le32 NextCommand; /* compound 요청에서 다음 명령 오프셋 */
__le64 MessageId; /* 요청-응답 매칭 ID */
__le32 ProcessId; /* 클라이언트 프로세스 ID */
__le32 TreeId; /* 공유 연결 ID */
__le64 SessionId; /* 세션 ID */
__u8 Signature[16]; /* HMAC/CMAC 서명 */
};
0xFD534D42)가 추가됩니다. 이 헤더에는 AES nonce, 원본 메시지 크기, 암호화 플래그가 포함되어 있으며, 뒤따르는 페이로드 전체가 AES-CCM/GCM으로 암호화됩니다.
주요 SMB2 명령
| 명령 | 코드 | 기능 | ksmbd 핸들러 |
|---|---|---|---|
| NEGOTIATE | 0x0000 | 프로토콜 버전/암호화 협상 | smb2_negotiate() |
| SESSION_SETUP | 0x0001 | 인증 (NTLM/Kerberos) | smb2_sess_setup() |
| LOGOFF | 0x0002 | 세션 종료 | smb2_session_logoff() |
| TREE_CONNECT | 0x0003 | 공유 폴더 연결 | smb2_tree_connect() |
| TREE_DISCONNECT | 0x0004 | 공유 연결 해제 | smb2_tree_disconnect() |
| CREATE | 0x0005 | 파일/디렉터리 열기/생성 | smb2_open() |
| CLOSE | 0x0006 | 파일 핸들 닫기 | smb2_close() |
| FLUSH | 0x0007 | 버퍼 플러시 | smb2_flush() |
| READ | 0x0008 | 파일 읽기 | smb2_read() |
| WRITE | 0x0009 | 파일 쓰기 | smb2_write() |
| LOCK | 0x000A | 바이트 범위 잠금 | smb2_lock() |
| IOCTL | 0x000B | 파일시스템 제어 (DFS, Copychunk) | smb2_ioctl() |
| CANCEL | 0x000C | 비동기 요청 취소 | smb2_cancel() |
| ECHO | 0x000D | 연결 유효성 확인 | smb2_echo() |
| QUERY_DIRECTORY | 0x000E | 디렉터리 목록 | smb2_query_dir() |
| CHANGE_NOTIFY | 0x000F | 디렉터리 변경 감시 | smb2_notify() |
| QUERY_INFO | 0x0010 | 파일/공유/보안 정보 조회 | smb2_query_info() |
| SET_INFO | 0x0011 | 파일/보안 정보 설정 | smb2_set_info() |
| OPLOCK_BREAK | 0x0012 | Oplock/Lease Break 알림 | smb2_oplock_break() |
ksmbd 아키텍처
모듈 구성 요소
ksmbd.ko는 내부적으로 여러 서브시스템으로 나뉘어 있으며, 각각 독립적인 역할을 수행합니다.
ksmbd 워커 스레드 모델
ksmbd는 커널 워크큐(ksmbd_io_wq)를 사용하여 SMB 요청을 병렬 처리합니다. 연결당 하나의 수신 스레드가 요청을 읽어 워크큐에 제출하고, 워커 스레드가 실제 처리를 수행합니다.
/* ksmbd 스레드 구조 */
/* ksmbd_listener_service: TCP 연결 수락 (포트 445) */
struct ksmbd_transport_ops tcp_transport_ops = {
.writev = tcp_writev,
.read = tcp_read,
.disconnect = tcp_disconnect,
};
/* 연결당 ksmbd_conn 생성 */
struct ksmbd_conn {
struct smb_version_ops *ops; /* SMB2/3 버전별 처리 함수 */
struct smb_version_cmds *cmds; /* 명령 핸들러 테이블 */
struct ksmbd_transport *transport; /* TCP/RDMA 전송 계층 */
struct list_head sessions; /* 이 연결의 세션 목록 */
struct nls_table *local_nls; /* 문자셋 변환 */
atomic_t req_running; /* 동시 처리 중인 요청 수 */
};
/* 요청당 ksmbd_work 생성 → 워크큐에서 처리 */
struct ksmbd_work {
struct ksmbd_conn *conn;
struct ksmbd_session *sess;
struct ksmbd_tree_connect *tcon;
struct kvec *iov; /* 요청/응답 버퍼 */
struct work_struct work; /* 커널 워크큐 */
};
워커 스레드풀 아키텍처
ksmbd의 요청 처리는 크게 세 단계로 구성됩니다: TCP 수신 스레드, 디코더, 워크큐 워커입니다. 각 연결에 대해 전용 커널 스레드(ksmbd:conn)가 생성되어 TCP 소켓에서 SMB 패킷을 수신하고, 디코딩 후 ksmbd_work를 할당하여 글로벌 워크큐에 제출합니다.
/* 워크큐 생성 — server.c */
static int ksmbd_server_init(void)
{
/* WQ_MEM_RECLAIM: 메모리 부족 시에도 워커 보장 */
/* WQ_UNBOUND: CPU 고정 없이 유휴 CPU에서 실행 */
ksmbd_wq = alloc_workqueue("ksmbd_io_wq",
WQ_MEM_RECLAIM | WQ_UNBOUND, 0);
if (!ksmbd_wq)
return -ENOMEM;
return 0;
}
/* 요청 처리 메인 루프 — connection.c */
static int ksmbd_conn_handler_loop(void *p)
{
struct ksmbd_conn *conn = (struct ksmbd_conn *)p;
struct ksmbd_work *work;
while (ksmbd_conn_alive(conn)) {
/* TCP 소켓에서 SMB 패킷 수신 */
int size = conn->transport->ops->read(conn->transport,
conn->request_buf,
conn->request_buflen);
if (size <= 0)
break;
/* ksmbd_work 할당 및 초기화 */
work = ksmbd_alloc_work_struct();
work->conn = conn;
/* 워크큐에 제출 */
INIT_WORK(&work->work, handle_ksmbd_work);
ksmbd_queue_work(work);
}
}
WQ_UNBOUND 워크큐를 사용하므로 워커 스레드가 특정 CPU에 고정되지 않습니다. 이는 NUMA 시스템에서 메모리 접근 패턴이 균일하지 않을 수 있지만, 소규모 NAS 환경에서는 CPU 활용률을 극대화합니다. 대규모 환경에서는 irqbalance와 함께 NIC IRQ를 특정 NUMA 노드에 고정하면 성능이 향상됩니다.
Netlink IPC 통신
ksmbd는 보안에 민감한 작업(인증, 사용자 계정 조회, 공유 설정)을 커널-사용자 공간 Netlink 통신으로 처리합니다. 이 설계는 최소 권한 원칙을 따르며, 커널 코드를 단순하게 유지합니다.
/* ksmbd Netlink 패밀리 정의 */
static const struct genl_family ksmbd_genl_family = {
.name = KSMBD_GENL_NAME, /* "SMBD_GENL" */
.version = KSMBD_GENL_VERSION,
.ops = ksmbd_genl_ops,
.n_ops = ARRAY_SIZE(ksmbd_genl_ops),
};
/* Netlink 이벤트 타입 */
enum ksmbd_event {
KSMBD_EVENT_UNSPEC = 0,
KSMBD_EVENT_HEARTBEAT_REQUEST, /* 데몬 살아있는지 확인 */
KSMBD_EVENT_LOGIN_REQUEST, /* 사용자 로그인 요청 */
KSMBD_EVENT_LOGIN_RESPONSE, /* 패스워드 해시 반환 */
KSMBD_EVENT_SHARE_CONFIG_REQUEST, /* 공유 설정 조회 */
KSMBD_EVENT_SHARE_CONFIG_RESPONSE,
KSMBD_EVENT_TREE_CONNECT_REQUEST,
KSMBD_EVENT_LOGOUT_REQUEST,
KSMBD_EVENT_RPC_REQUEST, /* DCE/RPC 프록시 */
KSMBD_EVENT_RPC_RESPONSE,
};
Netlink 통신 흐름
SMB 클라이언트 ksmbd.ko ksmbd.mountd
─────────────────────────────────────────────────────────────────────
SESSION_SETUP → (1) KSMBD_EVENT_LOGIN_REQUEST →
(NTLM/Kerberos blob) [username, challenge]
← (2) KSMBD_EVENT_LOGIN_RESPONSE
[NT hash, domain, flags]
↕ DB 조회
ksmbdpwd.db
TREE_CONNECT → (3) KSMBD_EVENT_SHARE_CONFIG_REQUEST →
[share name]
← (4) KSMBD_EVENT_SHARE_CONFIG_RESPONSE
[path, flags, ACL]
Netlink 메시지 구조 상세
/* LOGIN_REQUEST 메시지에 포함되는 속성 */
struct ksmbd_login_request {
__u32 handle; /* Netlink 핸들 (응답 매칭) */
char account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* 사용자 이름 */
};
/* LOGIN_RESPONSE: ksmbd.mountd가 반환하는 인증 정보 */
struct ksmbd_login_response {
__u32 handle;
__u32 gid; /* 사용자 GID */
__u32 uid; /* 사용자 UID */
__u8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ];
__u16 status; /* 인증 결과 */
__u16 hash_sz; /* NT 해시 크기 (16바이트) */
__u8 hash[KSMBD_REQ_MAX_HASH_SZ]; /* MD4(password) */
};
/* SHARE_CONFIG_RESPONSE: 공유 설정 정보 */
struct ksmbd_share_config_response {
__u32 handle;
__u32 flags; /* 읽기전용, 게스트 허용 등 */
__u16 create_mask; /* 파일 생성 퍼미션 마스크 */
__u16 directory_mask; /* 디렉터리 생성 퍼미션 */
__u16 force_uid;
__u16 force_gid;
__u32 veto_list_sz; /* 숨김 파일 패턴 크기 */
__u8 path[KSMBD_REQ_MAX_SHARE_NAME]; /* 공유 경로 */
};
systemd의 Restart=always를 설정하여 자동 복구하는 것이 권장됩니다.
SMB2/3 Negotiate 협상
SMB 세션은 Negotiate 단계에서 시작합니다. 클라이언트가 지원하는 프로토콜 버전, 보안 기능, 암호화 알고리즘 목록을 서버에 전송하면, 서버는 가장 높은 공통 버전을 선택하여 응답합니다.
Negotiate Context 상세
SMB 3.1.1에서 도입된 Negotiate Context는 프로토콜 협상 시 추가 보안 옵션을 교환하는 구조체입니다.
| Context 타입 | ID | 설명 |
|---|---|---|
| PREAUTH_INTEGRITY_CAPABILITIES | 0x0001 | Pre-authentication 무결성 해시 알고리즘 (SHA-512) |
| ENCRYPTION_CAPABILITIES | 0x0002 | 암호화 알고리즘 (AES-128-CCM, AES-128-GCM, AES-256-CCM, AES-256-GCM) |
| COMPRESSION_CAPABILITIES | 0x0003 | 압축 알고리즘 (LZ77, LZ77+Huffman, LZNT1, Pattern_V1) |
| NETNAME_NEGOTIATE_CONTEXT | 0x0005 | 클라이언트가 연결하는 서버 이름 |
| SIGNING_CAPABILITIES | 0x0008 | 서명 알고리즘 (AES-CMAC, AES-GMAC) |
/* ksmbd Negotiate 처리 — smb2pdu.c */
int smb2_handle_negotiate(struct ksmbd_work *work)
{
struct smb2_negotiate_req *req = smb2_get_msg(work->request_buf);
struct smb2_negotiate_rsp *rsp = smb2_get_msg(work->response_buf);
struct ksmbd_conn *conn = work->conn;
/* 지원하는 최고 dialect 선택 */
conn->dialect = ksmbd_lookup_dialect(req);
/* SMB 3.1.1이면 Negotiate Context 처리 */
if (conn->dialect == SMB311_PROT_ID) {
decode_preauth_neg_context(conn, req);
decode_encrypt_neg_context(conn, req);
decode_sign_neg_context(conn, req);
decode_compress_neg_context(conn, req);
}
/* 응답 작성 */
rsp->DialectRevision = cpu_to_le16(conn->dialect);
rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
/* Pre-authentication Integrity Hash 초기화 */
ksmbd_gen_preauth_integrity_hash(conn,
work->request_buf, conn->preauth_info->Preauth_HashValue);
ksmbd_gen_preauth_integrity_hash(conn,
work->response_buf, conn->preauth_info->Preauth_HashValue);
return 0;
}
Kerberos/NTLM 인증 상세
ksmbd는 SPNEGO(Simple and Protected GSSAPI Negotiation Mechanism) 프레임워크를 통해 NTLM과 Kerberos 인증을 모두 지원합니다. 실제 인증 로직의 핵심 부분은 커널에서 처리하되, 사용자 DB 조회는 ksmbd.mountd에 위임합니다.
NTLMv2 키 파생 과정
/* NTLMv2 인증 키 파생 — auth.c */
/* 1단계: ResponseKeyNT 생성 */
/* NTOWFv2 = HMAC_MD5(MD4(Password), UPPER(UserName) + Domain) */
ksmbd_gen_ntlmv2_hash(sess, sess->user->passkey, nt_hash);
/* 2단계: NTProofStr 검증 */
/* NTProofStr = HMAC_MD5(ResponseKeyNT, ServerChallenge + ClientBlob) */
ksmbd_auth_ntlmv2(sess, ntlmv2_resp, ntlmv2_resp_len,
conn->ntlmssp.cryptkey);
/* 3단계: 세션 키 생성 */
/* SessionBaseKey = HMAC_MD5(ResponseKeyNT, NTProofStr) */
ksmbd_gen_session_key(sess);
/* 4단계: SMB3 서명/암호화 키 파생 (KDF) */
/* SigningKey = KDF(SessionBaseKey, "SMBSigningKey\0", PreauthHash) */
/* EncryptionKey = KDF(SessionBaseKey, "SMBC2SCipherKey\0", PreauthHash) */
/* DecryptionKey = KDF(SessionBaseKey, "SMBS2CCipherKey\0", PreauthHash) */
ksmbd_gen_smb311_signingkey(sess, conn);
Kerberos 인증
| 단계 | 설명 | 관련 함수 |
|---|---|---|
| 1. AP-REQ 수신 | 클라이언트가 KDC에서 받은 Service Ticket을 SPNEGO 토큰으로 전달 | ksmbd_krb5_authenticate() |
| 2. Keytab 검증 | ksmbd.mountd가 /etc/krb5.keytab으로 티켓 복호화 검증 | Netlink RPC를 통해 위임 |
| 3. PAC 검증 | Privilege Attribute Certificate에서 그룹 멤버십 추출 | ksmbd_krb5_extract_user() |
| 4. 세션 키 설정 | Kerberos 세션 키를 SMB 서명/암호화에 사용 | ksmbd_gen_krb5_session_key() |
# Kerberos 인증을 위한 ksmbd 설정
[global]
server signing = required
kerberos keytab = /etc/krb5.keytab
kerberos service name = cifs
# keytab 생성 (Active Directory 환경)
$ ktpass -princ cifs/fileserver.example.com@EXAMPLE.COM \
-mapuser EXAMPLE\fileserver$ -crypto AES256-SHA1 \
-ptype KRB5_NT_PRINCIPAL \
-out /etc/krb5.keytab
# keytab 확인
$ klist -kt /etc/krb5.keytab
Keytab name: FILE:/etc/krb5.keytab
KVNO Timestamp Principal
---- ---------- -----------------------------------------------
2 03/01/26 cifs/fileserver.example.com@EXAMPLE.COM
VFS 통합
ksmbd는 VFS 레이어를 통해 하위 파일시스템(ext4, XFS, Btrfs 등)과 독립적으로 동작합니다. kern_path(), vfs_read(), vfs_write() 등의 VFS API를 직접 호출합니다.
주요 VFS 호출 매핑
| SMB 명령 | VFS 함수 | 설명 |
|---|---|---|
| CREATE (파일 열기) | vfs_open(), dentry_open() | inode 조회 및 file 객체 생성 |
| READ | vfs_read(), kernel_read() | 파일 내용 읽기 |
| WRITE | vfs_write(), kernel_write() | 파일 내용 쓰기 |
| QUERY_INFO | vfs_getattr() | 파일 속성(크기, 시간, 권한) 조회 |
| SET_INFO | vfs_setattr(), vfs_utimes() | 파일 속성 변경 |
| QUERY_DIRECTORY | iterate_dir() | 디렉터리 목록 열거 |
| CREATE (디렉터리) | vfs_mkdir() | 디렉터리 생성 |
| IOCTL (DFS) | vfs_ioctl() | 파일시스템 특수 명령 |
| 파일 잠금 | vfs_lock_file() | POSIX/CIFS 잠금 |
| 확장 속성 | vfs_getxattr(), vfs_setxattr() | 보안 레이블, ACL 저장 |
| 파일 삭제 | vfs_unlink() | 파일 제거 |
| 이름 변경 | vfs_rename() | 파일/디렉터리 이동 |
| 심볼릭 링크 | vfs_symlink(), vfs_readlink() | 심볼릭 링크 생성/읽기 |
파일 핸들 캐시 (vfs_cache.c)
ksmbd는 SMB 파일 ID(volatile + persistent)와 커널 struct file 간의 매핑을 ksmbd_file 구조체로 관리합니다. 해시 테이블 기반의 빠른 조회를 지원합니다.
/* 파일 핸들 구조체 — vfs_cache.c */
struct ksmbd_file {
struct file *filp; /* VFS file 객체 */
char *filename; /* 파일 경로 */
u64 persistent_id; /* SMB persistent file ID */
u64 volatile_id; /* SMB volatile file ID */
struct ksmbd_inode *f_ci; /* ksmbd inode 정보 */
struct oplock_info *f_opinfo; /* oplock/lease 상태 */
struct ksmbd_conn *conn; /* 소속 연결 */
struct ksmbd_tree_connect *tcon; /* 소속 트리 연결 */
/* Durable Handle 지원 */
bool is_durable; /* durable handle 여부 */
bool is_persistent; /* persistent handle 여부 */
bool is_resilient; /* resilient handle 여부 */
struct hlist_node f_hash; /* 해시 테이블 노드 */
struct list_head node; /* 전체 목록 */
atomic_t refcount;
};
/* 빠른 조회: volatile ID → ksmbd_file */
struct ksmbd_file *ksmbd_lookup_fd_fast(
struct ksmbd_work *work, u64 volatile_id)
{
struct ksmbd_file *fp;
read_lock(&fp_table.lock);
fp = __ksmbd_lookup_fd(&fp_table, volatile_id);
read_unlock(&fp_table.lock);
return fp;
}
POSIX ACL 및 Windows DACL 매핑
Windows와 POSIX의 권한 모델은 근본적으로 다릅니다. ksmbd는 이 두 모델 간의 변환을 smbacl.c에서 처리합니다.
/* ksmbd ACL 처리 — smbacl.c */
/* Windows DACL ↔ POSIX ACL 변환 */
/* 파일 보안 디스크립터 조회 */
int smb_get_acl(struct ksmbd_work *work,
struct smb_ntsd **pntsd,
int *pntsd_size, struct dentry *dentry)
{
struct posix_acl *pacl;
struct smb_sid owner_sid;
/* 파일 owner의 UID → Windows SID 변환 */
uid_to_sid(i_uid_into_mnt(idmap, inode), &owner_sid);
/* POSIX ACL → Windows DACL 변환 */
pacl = get_acl(inode, ACL_TYPE_ACCESS);
if (pacl)
posix_acl_to_dacl(pacl, dacl, flags);
}
/* SID 매핑 테이블 */
/* 주요 Well-Known SID */
/* S-1-1-0 : Everyone → mode bits 'other' */
/* S-1-3-0 : Creator Owner → POSIX owner */
/* S-1-3-1 : Creator Group → POSIX group */
/* S-1-5-18 : NT AUTHORITY\SYSTEM → uid 0 */
/* S-1-5-21-...-500 : Administrator → uid 0 */
| Windows 권한 | 비트 값 | POSIX 매핑 |
|---|---|---|
| FILE_READ_DATA | 0x0001 | r (4) |
| FILE_WRITE_DATA | 0x0002 | w (2) |
| FILE_EXECUTE | 0x0020 | x (1) |
| FILE_DELETE | 0x10000 | 부모 디렉터리 w+x |
| FILE_READ_ATTRIBUTES | 0x0080 | stat() 가능 |
| GENERIC_ALL | 0x10000000 | rwx (7) |
| GENERIC_READ | 0x80000000 | r (4) |
| GENERIC_WRITE | 0x40000000 | w (2) |
세션 관리
ksmbd는 SMB2 세션 수립 과정(Negotiate → SessionSetup → TreeConnect)을 커널에서 직접 처리합니다. 세션 객체는 3계층 구조(Connection → Session → TreeConnect)로 관리됩니다.
세션 객체 계층 구조
/* 연결 → 세션 → 트리 연결 3계층 구조 */
/* mgmt/user_session.c */
struct ksmbd_session {
u64 id; /* SMB SessionId */
struct ksmbd_user *user; /* 인증된 사용자 */
struct ksmbd_conn *conn; /* 소속 연결 */
struct list_head ksmbd_chann_list; /* 멀티채널 연결 목록 */
struct xarray tree_conns; /* 트리 연결 (XArray) */
struct ksmbd_sign_ctx sign_ctx; /* 서명 컨텍스트 */
/* 세션 키 (암호화/서명에 사용) */
__u8 sess_key[CIFS_KEY_SIZE]; /* 16바이트 세션 베이스 키 */
__u8 smb3signingkey[SMB3_SIGN_KEY_SIZE]; /* 서명 키 */
__u8 smb3encryptionkey[SMB3_ENC_KEY_SIZE]; /* 암호화 키 */
__u8 smb3decryptionkey[SMB3_DEC_KEY_SIZE]; /* 복호화 키 */
int state; /* SESSION_NEW / SESSION_VALID / SESSION_EXPIRED */
};
/* mgmt/tree_connect.c */
struct ksmbd_tree_connect {
int id; /* TreeId */
struct ksmbd_share_config *share_conf; /* 공유 설정 */
struct ksmbd_user *user; /* 사용자 */
struct path share_path; /* 공유 루트 경로 */
unsigned int maximal_access; /* 최대 접근 권한 */
atomic_t refcount;
};
Oplock과 Lease 메커니즘
Oplock(Opportunistic Lock)과 Lease는 SMB 클라이언트의 파일 캐싱을 제어하는 핵심 메커니즘입니다. 클라이언트가 파일을 독점 사용할 때 로컬에서 캐싱하도록 허용하고, 다른 클라이언트가 접근하면 캐시를 무효화(break)하여 일관성을 보장합니다.
Oplock 유형
| Oplock 유형 | 읽기 캐시 | 쓰기 캐시 | 핸들 캐시 | 설명 |
|---|---|---|---|---|
| None | 아니오 | 아니오 | 아니오 | 캐싱 없음 — 모든 I/O가 서버 경유 |
| Level II (Shared) | 예 | 아니오 | 아니오 | 읽기 캐시만 — 여러 클라이언트 공유 가능 |
| Exclusive | 예 | 예 | 아니오 | 독점 — 읽기/쓰기 모두 로컬 캐시 |
| Batch | 예 | 예 | 예 | 독점 + 핸들 캐시 (반복 열기 최적화) |
SMB2 Lease (Directory Lease 포함)
Lease는 SMB2.1에서 도입된 oplock의 진화형입니다. 파일 핸들이 아닌 Lease Key(16바이트 UUID) 기반으로 동작하여, 같은 파일의 여러 핸들을 하나의 lease로 관리할 수 있습니다.
| Lease State | 비트 | 의미 |
|---|---|---|
| READ (R) | 0x01 | 읽기 캐시 허용. 로컬에서 읽기 가능 |
| WRITE (W) | 0x04 | 쓰기 캐시 허용. 로컬에서 쓰기 후 나중에 플러시 |
| HANDLE (H) | 0x02 | 핸들 캐시 허용. close 후에도 서버가 핸들 유지 |
| R-W-H | 0x07 | 모든 캐싱 활성화 (Exclusive 상태) |
| R-H | 0x03 | 쓰기 break 후 읽기+핸들만 유지 |
| R | 0x01 | 쓰기+핸들 break 후 읽기만 유지 |
/* oplock.c — Lease Break 처리 */
static int opinfo_write_to_read(struct oplock_info *opinfo)
{
/* RWH → RH → R: 단계적 break */
if (opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
/* 클라이언트에게 OPLOCK_BREAK 통지 전송 */
smb2_send_oplock_break(opinfo->work, opinfo);
/* 클라이언트 응답 대기 (타임아웃: 35초) */
wait_for_break_ack(opinfo);
/* 레벨 하향 */
opinfo->level = SMB2_OPLOCK_LEVEL_II;
}
return 0;
}
/* Lease Break 타임아웃 */
#define OPLOCK_WAIT_TIME (35 * HZ) /* MS-SMB2 명세: 35초 */
dmesg에 "oplock break timeout" 메시지가 반복된다면 네트워크 상태를 점검하세요.
암호화 및 서명
SMB3는 전송 중 데이터 보호를 위해 패킷 서명(signing)과 암호화(encryption)를 제공합니다. ksmbd는 커널 Crypto API를 활용하여 이를 구현합니다.
패킷 서명
| SMB 버전 | 서명 알고리즘 | 키 파생 |
|---|---|---|
| SMB2.0 / 2.1 | HMAC-SHA256 | SessionKey 직접 사용 |
| SMB3.0 / 3.0.2 | AES-128-CMAC | KDF(SessionKey, "SMB2AESCMAC" + "SmbSign") |
| SMB3.1.1 | AES-128-CMAC 또는 AES-128-GMAC | KDF(SessionKey, "SMBSigningKey" + PreauthHash) |
/* 서명 생성 — crypto_ctx.c */
int ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn,
char *key, struct kvec *iov,
int n_vec, char *sig)
{
struct ksmbd_crypto_ctx *ctx;
ctx = ksmbd_crypto_ctx_find(conn->dialect);
if (conn->dialect >= SMB30_PROT_ID) {
/* SMB3+: AES-128-CMAC */
crypto_shash_setkey(ctx->cmac, key, SMB2_CMAC_KEY_SIZE);
crypto_shash_init(ctx->sdesc);
for (int i = 0; i < n_vec; i++)
crypto_shash_update(ctx->sdesc, iov[i].iov_base, iov[i].iov_len);
crypto_shash_final(ctx->sdesc, sig);
} else {
/* SMB2: HMAC-SHA256 */
crypto_shash_setkey(ctx->hmac_sha256, key, SMB2_HMAC_KEY_SIZE);
/* ... 동일한 update/final 패턴 ... */
}
return 0;
}
SMB3 암호화 상세
SMB3 암호화가 활성화되면 SMB2 헤더 앞에 Transform Header가 추가되고, 이후 전체 페이로드가 AEAD(Authenticated Encryption with Associated Data)로 암호화됩니다.
# /etc/ksmbd/smb.conf
[global]
server signing = required # SMB 서명 강제 (HMAC-SHA256 / AES-CMAC)
encrypt = true # SMB3 암호화 활성화 (AES-128-CCM/GCM)
server min protocol = SMB3 # SMB3 미만 연결 거부
[share]
path = /data/share
read only = no
valid users = alice, bob
create mask = 0660
directory mask = 0770
| 기능 | 알고리즘 | SMB 버전 | ksmbd Crypto API |
|---|---|---|---|
| 패킷 서명 | HMAC-SHA256 | SMB2.0~3.0 | hmac(sha256) |
| 패킷 서명 | AES-128-CMAC | SMB3.0+ | cmac(aes) |
| 암호화 | AES-128-CCM | SMB3.0 | ccm(aes) |
| 암호화 | AES-128-GCM | SMB3.1.1 | gcm(aes) |
| 암호화 | AES-256-CCM | SMB3.1.1 | ccm(aes) |
| 암호화 | AES-256-GCM | SMB3.1.1 | gcm(aes) |
| Pre-auth 무결성 | SHA-512 | SMB3.1.1 | sha512 |
/* SMB3 Transform Header 구조 */
struct smb2_transform_hdr {
__le32 ProtocolId; /* 0xFD534D42 "\xFDSMB" */
__u8 Signature[16]; /* AES-CCM/GCM MAC 태그 */
__u8 Nonce[16]; /* CCM: 11바이트 nonce, GCM: 12바이트 nonce */
__le32 OriginalMessageSize; /* 암호화 전 원본 크기 */
__le16 Reserved;
__le16 Flags; /* 0x0001: 암호화됨 */
__le64 SessionId; /* 복호화 키 식별용 */
};
/* 암호화 처리 — smb2pdu.c */
int smb3_encrypt_resp(struct ksmbd_work *work)
{
struct smb2_transform_hdr *tr_hdr;
struct aead_request *req;
/* Transform Header 구성 */
tr_hdr->ProtocolId = SMB2_TRANSFORM_PROTO_NUM;
get_random_bytes(tr_hdr->Nonce, 16);
tr_hdr->SessionId = work->sess->id;
/* AEAD 암호화 (AES-GCM 또는 AES-CCM) */
crypto_aead_setkey(tfm, work->sess->smb3encryptionkey,
SMB3_ENC_KEY_SIZE);
crypto_aead_encrypt(req);
return 0;
}
aes-ni 모듈이 자동으로 활성화되어 암호화 오버헤드가 최소화됩니다. cat /proc/crypto | grep -A4 "name.*gcm(aes)"로 하드웨어 가속 여부를 확인할 수 있습니다. 가속이 없으면 처리량이 30~50% 감소할 수 있습니다.
SMB3 멀티채널
SMB3 멀티채널은 하나의 SMB 세션을 여러 TCP 연결로 묶어 대역폭을 집계하고 네트워크 장애 시 투명한 페일오버를 제공합니다. ksmbd는 server multi channel support = yes 설정으로 이를 활성화합니다.
# 서버 설정 — /etc/ksmbd/smb.conf
[global]
server multi channel support = yes
# 클라이언트 마운트 (Linux cifs)
$ mount -t cifs //server/share /mnt \
-o username=alice,vers=3.1.1,multichannel,max_channels=4
# 멀티채널 상태 확인 (클라이언트)
$ cat /proc/fs/cifs/Stats
Sessions: 1
Channels: 4 (10.0.0.10:445, 10.0.1.10:445, 10.0.2.10:445, 192.168.0.10:445)
채널 바인딩 과정
/* 멀티채널 바인딩 — smb2pdu.c */
/* 추가 채널은 SESSION_SETUP에 SMB2_SESSION_FLAG_BINDING 플래그로 요청 */
int smb2_sess_setup(struct ksmbd_work *work)
{
struct smb2_sess_setup_req *req;
u64 sess_id = le64_to_cpu(req->hdr.SessionId);
if (req->Flags & SMB2_SESSION_FLAG_BINDING) {
/* 기존 세션에 새 연결(채널) 바인딩 */
sess = ksmbd_session_lookup(conn, sess_id);
/* 채널 바인딩 검증: */
/* 1. 동일 사용자 인증 필수 */
/* 2. Pre-auth Integrity Hash로 바인딩 무결성 검증 */
ksmbd_verify_channel_binding(conn, sess);
/* 채널 목록에 추가 */
ksmbd_session_register_channel(sess, conn);
}
}
Durable Handle과 Persistent Handle
Durable Handle은 네트워크 일시 단절 시 파일 핸들을 보존하여, 재연결 후 파일 상태를 복구할 수 있게 합니다. ksmbd는 Durable Handle v1/v2와 Persistent Handle을 모두 지원합니다.
| 핸들 유형 | 도입 버전 | 재연결 시간 | 클러스터 지원 | 설명 |
|---|---|---|---|---|
| Durable Handle v1 | SMB2.1 | 최대 60초 | 아니오 | 단일 서버 내 재연결 |
| Durable Handle v2 | SMB3.0 | 설정 가능 | 아니오 | Create GUID로 핸들 식별 |
| Persistent Handle | SMB3.0 | 무제한 | 예 | 클러스터 페일오버 대응 |
| Resilient Handle | SMB2.1 | 최대 300초 | 아니오 | IOCTL로 요청, oplock 불필요 |
/* Durable Handle 재연결 — smb2pdu.c (smb2_open) */
/* CREATE 요청에 SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 컨텍스트 포함 시 */
if (durable_v2_ctx) {
/* 1. Create GUID로 기존 핸들 검색 */
fp = ksmbd_lookup_durable_fd(durable_v2_ctx->CreateGuid);
if (!fp)
return -ENOENT; /* STATUS_OBJECT_NAME_NOT_FOUND */
/* 2. 소유권 검증 */
if (fp->create_guid != durable_v2_ctx->CreateGuid)
return -EACCES;
/* 3. 새 세션/연결에 핸들 바인딩 */
fp->conn = work->conn;
fp->sess = work->sess;
fp->tcon = work->tcon;
/* 4. oplock/lease 상태 복원 */
smb2_restore_oplock(fp);
}
/* Durable Handle 해제 타임아웃 */
/* 연결 해제 후 timeout 이내에 재연결하지 않으면 핸들 삭제 */
static void ksmbd_durable_scavenger(struct work_struct *work)
{
/* 만료된 durable handle 정리 */
list_for_each_entry_safe(fp, tmp, &durable_list, node) {
if (time_after(jiffies, fp->durable_timeout)) {
ksmbd_close_fd(fp);
ksmbd_release_oplock(fp);
}
}
}
Compound Request 처리
SMB2 Compound Request는 여러 SMB 명령을 하나의 TCP 패킷에 묶어 전송하는 최적화 기법입니다. 네트워크 왕복 횟수를 줄여 특히 높은 지연 환경에서 성능을 크게 향상시킵니다.
Compound 유형
| 유형 | 설명 | 예시 |
|---|---|---|
| Related Compound | 이전 명령의 결과(FileId 등)를 다음 명령이 참조. SMB2_FLAGS_RELATED_OPERATIONS 플래그 설정 | CREATE + READ + CLOSE (한 번의 RTT로 파일 읽기) |
| Unrelated Compound | 독립적인 명령들의 묶음. 각각 별도의 FileId/TreeId 사용 | QUERY_INFO(file1) + QUERY_INFO(file2) |
/* Compound Request 처리 — smb2pdu.c */
static int handle_ksmbd_work(struct ksmbd_work *work)
{
struct smb2_hdr *hdr;
int next_cmd_offset;
do {
hdr = smb2_get_msg(work->request_buf + work->next_smb2_rcv_hdr_off);
/* Related compound: 이전 결과의 FileId 자동 참조 */
if (hdr->Flags & SMB2_FLAGS_RELATED_OPERATIONS) {
/* FileId가 0xFFFFFFFFFFFFFFFF이면 이전 CREATE의 결과 사용 */
if (le64_to_cpu(hdr->FileId.Volatile) == 0xFFFFFFFFFFFFFFFF)
work->cur_local_fid = work->compound_fid;
}
/* 명령 디스패치 */
__smb2_handle_command(work);
/* 다음 compound 명령으로 이동 */
next_cmd_offset = le32_to_cpu(hdr->NextCommand);
if (next_cmd_offset) {
work->next_smb2_rcv_hdr_off += next_cmd_offset;
work->next_smb2_rsp_hdr_off += get_rsp_size(work);
}
} while (next_cmd_offset);
}
/* Related Compound 예시: CREATE + READ + CLOSE */
/* 요청 1: CREATE file.txt → FileId=0x1234 (compound_fid에 저장) */
/* 요청 2: READ FileId=0xFFFFFFFF → 자동으로 0x1234 대체 */
/* 요청 3: CLOSE FileId=0xFFFFFFFF → 자동으로 0x1234 대체 */
CREATE + QUERY_INFO + QUERY_DIRECTORY + CLOSE의 compound request를 사용합니다. 이를 통해 4번의 RTT를 1번으로 줄입니다. WAN 환경(RTT 50ms)에서 100개 파일 목록을 가져올 때, compound 없이는 200ms(4*50), compound로는 50ms로 4배 빠릅니다.
Change Notify 구현
SMB2 CHANGE_NOTIFY는 디렉터리 변경을 감시하여 클라이언트에 알리는 비동기 명령입니다. Windows Explorer의 폴더 자동 새로고침이 이 기능에 의존합니다. ksmbd는 VFS의 fsnotify 인프라를 활용합니다.
Change Notify 필터
| 필터 | 비트 | 감시 대상 |
|---|---|---|
| FILE_NOTIFY_CHANGE_FILE_NAME | 0x001 | 파일 생성, 삭제, 이름 변경 |
| FILE_NOTIFY_CHANGE_DIR_NAME | 0x002 | 디렉터리 생성, 삭제, 이름 변경 |
| FILE_NOTIFY_CHANGE_ATTRIBUTES | 0x004 | 파일 속성 변경 |
| FILE_NOTIFY_CHANGE_SIZE | 0x008 | 파일 크기 변경 |
| FILE_NOTIFY_CHANGE_LAST_WRITE | 0x010 | 최종 수정 시간 변경 |
| FILE_NOTIFY_CHANGE_SECURITY | 0x100 | 보안 디스크립터 변경 |
/* Change Notify 요청 처리 — smb2pdu.c */
int smb2_notify(struct ksmbd_work *work)
{
struct smb2_change_notify_req *req;
struct ksmbd_file *fp;
req = smb2_get_msg(work->request_buf);
fp = ksmbd_lookup_fd_fast(work, le64_to_cpu(req->VolatileFileId));
/* 비동기 처리: INTERIM 응답 전송 */
smb2_send_interim_resp(work, STATUS_PENDING);
/* fsnotify를 통한 디렉터리 감시 등록 */
ksmbd_register_dir_notify(fp,
le32_to_cpu(req->CompletionFilter),
req->Flags & SMB2_WATCH_TREE); /* 재귀 감시 */
/* 변경 발생까지 대기 (비동기) */
wait_event_interruptible(fp->notify_waitq,
!list_empty(&fp->notify_list));
/* 변경 목록을 응답에 채움 */
ksmbd_fill_notify_info(work, fp);
return 0;
}
I/O 경로
ksmbd의 파일 I/O는 SMB 명령을 VFS 호출로 변환하는 과정입니다. 읽기/쓰기 경로는 커널 페이지 캐시를 활용합니다.
/* SMB2 WRITE 명령 처리 예시 */
int smb2_write(struct ksmbd_work *work)
{
struct smb2_write_req *req = smb2_get_msg(work->request_buf);
struct ksmbd_file *fp;
loff_t pos = le64_to_cpu(req->Offset);
size_t count = le32_to_cpu(req->Length);
/* 파일 핸들 조회 */
fp = ksmbd_lookup_fd_fast(work, le64_to_cpu(req->VolatileFileId));
/* oplock 확인 및 lease break 처리 */
smb_break_all_write_oplock(work, fp, 1);
/* VFS 쓰기 (페이지 캐시 경유) */
ksmbd_vfs_write(work, fp, smb2_get_data_area_len(req),
count, &pos, le32_to_cpu(req->WriteThrough));
}
/* 읽기 경로 — splice/sendfile 최적화 */
int ksmbd_vfs_read(struct ksmbd_work *work,
struct ksmbd_file *fp,
size_t count, loff_t *pos)
{
/* 대용량 읽기 시 splice를 사용하여 */
/* 커널 버퍼 → TCP 소켓 직접 전송 (제로카피 근접) */
if (work->conn->vals->large_lock_type) {
return ksmbd_vfs_splice_read(work, fp, count, pos);
}
return kernel_read(fp->filp, work->aux_payload_buf, count, pos);
}
splice 시스템 콜의 커널 내부 구현을 활용하여 페이지 캐시 → TCP 소켓 간 데이터를 사용자 공간 복사 없이 전송합니다. 이는 Samba가 커널↔사용자 공간 간 최소 2번의 복사를 수행하는 것과 비교하여 큰 파일 전송 시 CPU 사용량을 크게 줄입니다.
보안 기능
인증 방식
| 인증 | 설명 | 설정 |
|---|---|---|
| NTLM (NTLMv2) | NT 해시 기반 챌린지-응답 | 기본 활성화 |
| Kerberos | Active Directory 티켓 기반 | kerberos keytab = /etc/krb5.keytab |
| Guest 접근 | 인증 없이 읽기 전용 접근 | guest ok = yes |
보안 강화 체크리스트
# 1. SMB1 사용 불가 확인 (ksmbd는 기본적으로 지원 안 함)
$ grep -i "server min protocol" /etc/ksmbd/smb.conf
server min protocol = SMB3 # 권장: SMB3 이상만 허용
# 2. 서명 필수화
server signing = required
# 3. 암호화 활성화
encrypt = true
# 4. Guest 접근 비활성화
restrict anonymous = 2
map to guest = never
# 5. 특정 인터페이스에만 바인딩
interfaces = 10.0.0.0/24
bind interfaces only = yes
# 6. 최소 권한 공유 설정
[restricted_share]
path = /data/secure
valid users = @secure-group
read only = yes
create mask = 0640
directory mask = 0750
veto files = /*.exe/*.bat/*.cmd/*.vbs/
# 7. SELinux 컨텍스트 설정
$ semanage fcontext -a -t samba_share_t "/data/share(/.*)?"
$ restorecon -Rv /data/share
# 8. 방화벽 (firewalld)
$ firewall-cmd --permanent --add-service=samba
$ firewall-cmd --reload
rmmod ksmbd로 모듈을 언로드하세요.
ksmbd-tools 설정
# ksmbd-tools 설치 (Fedora/RHEL)
$ dnf install ksmbd-tools
# ksmbd-tools 설치 (Debian/Ubuntu)
$ apt install ksmbd-tools
# ksmbd-tools 설치 (소스 빌드)
$ git clone https://github.com/cifsd-team/ksmbd-tools.git
$ cd ksmbd-tools
$ ./autogen.sh && ./configure && make && sudo make install
# 사용자 추가
$ ksmbd.adduser -a alice
Password: ****
$ ksmbd.adduser -a bob
# 사용자 목록 확인
$ ksmbd.adduser -l
# 사용자 비밀번호 변경
$ ksmbd.adduser -u alice
# 사용자 삭제
$ ksmbd.adduser -d bob
# ksmbd 모듈 로드
$ modprobe ksmbd
# 데몬 시작 (공유 설정 서비스)
$ ksmbd.mountd
# 또는 systemd 서비스로 관리
$ systemctl enable --now ksmbd
# 설정 재로드 (ksmbd.mountd에게 SIGHUP)
$ ksmbd.control --reload
# 공유 목록 확인
$ ksmbd.control --list
# ksmbd 상태 확인
$ ksmbd.control --status
# ksmbd 종료
$ ksmbd.control --shutdown
smb.conf 고급 예제
[global]
workgroup = WORKGROUP
server string = Linux ksmbd Server
server min protocol = SMB2
server signing = auto # 클라이언트 요청 시 서명
max active sessions = 1000
smb2 max read = 8388608 # 8MB (성능 최적화)
smb2 max write = 8388608
smb2 max trans = 8388608
server multi channel support = yes
deadtime = 15 # 유휴 세션 타임아웃 (분)
smb2 leases = yes # SMB2 Lease 활성화
durable handle = yes # Durable Handle 지원
[homes]
comment = Home Directories
valid users = %S
browsable = no
read only = no
create mask = 0600
directory mask = 0700
[public]
comment = Public Share
path = /srv/smb/public
guest ok = yes
read only = yes
[backup]
comment = Backup Storage
path = /srv/smb/backup
valid users = @backup-group
read only = no
force group = backup-group
create mask = 0660
inherit acls = yes # 부모 디렉터리 ACL 상속
veto files = /._*/.DS_Store/Thumbs.db/
[media]
comment = Media Files
path = /srv/smb/media
valid users = @media-group
read only = yes
write list = admin
oplocks = yes
level2 oplocks = yes
ksmbd-tools 아키텍처
| 도구 | 역할 | 통신 |
|---|---|---|
ksmbd.mountd | 메인 데몬. smb.conf 파싱, Netlink 응답, DCE/RPC 프록시 | Netlink (SMBD_GENL) |
ksmbd.adduser | 사용자 추가/삭제/수정. ksmbdpwd.db 관리 | 직접 DB 접근 |
ksmbd.control | 런타임 제어. 공유 재로드, 종료, 상태 확인 | Netlink (SMBD_GENL) |
Samba vs ksmbd 비교
| 항목 | Samba | ksmbd |
|---|---|---|
| 구현 위치 | 사용자 공간 (smbd 프로세스) | 커널 공간 (ksmbd.ko) |
| SMB1 지원 | 지원 (선택 비활성화 가능) | 미지원 (SMB2+ 만) |
| Active Directory 통합 | 완전 지원 (winbind, sssd) | 제한적 (Kerberos만) |
| DFS (Distributed FS) | DFS 루트/링크 완전 지원 | DFS 루트만 지원 |
| CTDB 클러스터링 | 완전 지원 | 미지원 |
| 프린터 공유 | CUPS 통합 | 미지원 |
| SMB Direct (RDMA) | 실험적 | 지원 (CONFIG_SMB_SERVER_SMBDIRECT) |
| 멀티채널 | 지원 (4.4+) | 지원 |
| Durable Handle | 지원 | 지원 (v1, v2, Persistent) |
| 소규모 I/O 성능 | 기준 | +10~30% (컨텍스트 전환 감소) |
| 대규모 순차 I/O | 기준 | +5~15% (제로카피 근접) |
| 메모리 사용량 | 프로세스당 수십 MB | 커널 메모리 (동적, 수 MB) |
| 보안 영향 | 프로세스 격리 | 커널 취약점 → 시스템 전체 영향 |
| 설정 복잡도 | 복잡 (광범위한 옵션) | 단순 (NAS 특화) |
| 커뮤니티/지원 | 성숙, 광범위 | 성장 중 |
| 적합한 용도 | 엔터프라이즈 AD 환경 | 단순 NAS / 임베디드 |
smb.conf 형식을 부분적으로 호환하므로, 기존 Samba 설정의 일부를 재사용할 수 있습니다. 마이그레이션 시에는 ksmbd.adduser로 사용자를 재등록해야 합니다.
성능 튜닝
서버 측 튜닝
# 1. smb.conf 버퍼 크기 최적화
[global]
smb2 max read = 8388608 # 8MB (기본 1MB → 8MB)
smb2 max write = 8388608 # 대용량 순차 I/O 최적화
smb2 max trans = 8388608 # 디렉터리 목록 등 메타데이터
# 2. 커널 네트워크 파라미터
$ sysctl -w net.core.rmem_max=16777216
$ sysctl -w net.core.wmem_max=16777216
$ sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
$ sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
$ sysctl -w net.core.netdev_max_backlog=5000
# 3. 파일시스템 마운트 옵션 (서버 스토리지)
# ext4: noatime, journal_data_writeback
$ mount -o noatime,data=writeback /dev/sda1 /data/share
# XFS: noatime, logbufs (로그 버퍼 증가)
$ mount -o noatime,logbufs=8 /dev/sda1 /data/share
# 4. IRQ 밸런싱 (NUMA 환경)
$ systemctl start irqbalance
# 5. 투명 huge pages 비활성화 (잠재적 지연 감소)
$ echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
클라이언트 측 마운트 옵션
# Linux 클라이언트에서 마운트 (최적화 옵션)
$ mount -t cifs //server/share /mnt/smb \
-o username=alice,vers=3.1.1,noperm,cache=loose,\
rsize=8388608,wsize=8388608,noatime,\
handletimeout=120,bsize=1048576
# SMB3 멀티채널 활성화 (서버 설정)
# smb.conf에 추가
server multi channel support = yes
# 클라이언트에서 멀티채널 마운트
$ mount -t cifs //server/share /mnt -o multichannel,\
max_channels=4,username=alice
# 마운트 옵션 상세
# cache=loose — 적극적 캐싱 (단일 클라이언트 환경)
# cache=strict — oplock 기반 캐싱 (다중 클라이언트)
# cache=none — 캐싱 비활성화 (디버깅)
# seal — SMB3 암호화 강제
# resilienthandles — Resilient Handle 요청
# persistenthandles — Persistent Handle 요청
# 성능 측정 (fio)
$ fio --name=smb-seq --filename=/mnt/smb/testfile \
--rw=write --bs=1M --direct=1 --size=4G \
--numjobs=4 --group_reporting
# 소규모 랜덤 I/O 벤치마크
$ fio --name=smb-rand --filename=/mnt/smb/testfile \
--rw=randread --bs=4K --size=1G \
--numjobs=8 --iodepth=32 --group_reporting
성능 비교 참고 데이터
| 워크로드 | Samba (기준) | ksmbd (상대) | 비고 |
|---|---|---|---|
| 순차 읽기 (1MB 블록) | 100% | ~110% | 10G NIC, 단일 스트림 |
| 순차 쓰기 (1MB 블록) | 100% | ~108% | 페이지 캐시 writeback |
| 랜덤 읽기 (4K 블록) | 100% | ~125% | 컨텍스트 전환 절감 효과 큼 |
| 랜덤 쓰기 (4K 블록) | 100% | ~120% | 시스템 콜 오버헤드 제거 |
| 메타데이터 (stat/open/close) | 100% | ~130% | VFS 직접 접근 효과 |
| 다수 클라이언트 (50+) | 100% | ~105% | 워크큐 스케줄링 경합 |
커널 소스 구조
| 경로 | 내용 | 주요 함수/구조체 |
|---|---|---|
fs/ksmbd/ | ksmbd 루트 디렉터리 | — |
fs/ksmbd/server.c | TCP 서버, 연결 수락, 워커 스레드 | ksmbd_server_init() |
fs/ksmbd/connection.c | 연결 관리, 수신 루프 | ksmbd_conn_handler_loop() |
fs/ksmbd/smb2pdu.c | SMB2/3 PDU 처리 (명령 핸들러) | smb2_negotiate(), smb2_open() |
fs/ksmbd/smb2ops.c | SMB2 연산자 테이블, 프로토콜 협상 | smb2_0_server_ops |
fs/ksmbd/smb2misc.c | SMB2 헤더 검증, 크레딧 관리 | ksmbd_smb2_check_message() |
fs/ksmbd/auth.c | NTLM/Kerberos 인증 | ksmbd_auth_ntlmv2() |
fs/ksmbd/crypto_ctx.c | AES-CCM/GCM 암호화, HMAC 서명 | ksmbd_sign_smb2_pdu() |
fs/ksmbd/smbacl.c | Windows DACL ↔ POSIX ACL 변환 | smb_get_acl(), uid_to_sid() |
fs/ksmbd/mgmt/ | 세션·트리·파일 핸들 관리 | user_session.c, tree_connect.c |
fs/ksmbd/vfs.c | VFS 래퍼 함수 (vfs_read/write 등) | ksmbd_vfs_read(), ksmbd_vfs_write() |
fs/ksmbd/vfs_cache.c | 파일 핸들 캐시 | ksmbd_lookup_fd_fast() |
fs/ksmbd/oplock.c | oplock / SMB2 lease 구현 | opinfo_write_to_read() |
fs/ksmbd/transport_tcp.c | TCP 전송 계층 | tcp_transport_ops |
fs/ksmbd/transport_rdma.c | SMB Direct (RDMA) 전송 | smbd_transport_ops |
fs/ksmbd/transport_ipc.c | Netlink IPC 전송 | ksmbd_ipc_login_request() |
fs/ksmbd/ndr.c | NDR (Network Data Representation) 직렬화 | ndr_encode(), ndr_decode() |
fs/ksmbd/unicode.c | UTF-16LE ↔ UTF-8 변환 | smb_strndup_from_utf16() |
디버깅 도구
# ksmbd 로그 레벨 설정
$ echo 7 > /sys/module/ksmbd/parameters/debug_type
# 비트 마스크: 1=SMB2, 2=AUTH, 4=VFS, 8=OPLOCK, 16=IPC, 32=CONN
# 특정 서브시스템만 디버깅
$ echo 1 > /sys/module/ksmbd/parameters/debug_type # SMB2만
$ echo 10 > /sys/module/ksmbd/parameters/debug_type # AUTH + OPLOCK
$ echo 63 > /sys/module/ksmbd/parameters/debug_type # 모든 서브시스템
# dmesg로 ksmbd 로그 확인
$ dmesg -w | grep ksmbd
# ksmbd tracepoint 활성화
$ ls /sys/kernel/debug/tracing/events/ksmbd/
$ echo 1 > /sys/kernel/debug/tracing/events/ksmbd/smb2_command/enable
$ echo 1 > /sys/kernel/debug/tracing/events/ksmbd/enable # 전체
# tracepoint 출력 확인
$ cat /sys/kernel/debug/tracing/trace_pipe
# 활성 세션 확인
$ ksmbd.control --list
# Wireshark SMB 패킷 캡처 및 분석
$ tcpdump -i eth0 -w /tmp/smb.pcap port 445
# Wireshark에서 smb2 필터로 분석
# bpftrace로 SMB 쓰기 레이턴시 측정
$ bpftrace -e '
kprobe:ksmbd_vfs_write { @start[tid] = nsecs; }
kretprobe:ksmbd_vfs_write /@start[tid]/ {
@usecs = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
# bpftrace로 SMB 명령별 빈도 추적
$ bpftrace -e '
kprobe:__smb2_handle_command {
@cmd_count[arg1] = count();
}
interval:s:10 { print(@cmd_count); clear(@cmd_count); }'
# smbclient로 연결 테스트
$ smbclient -L //server -U alice
$ smbclient //server/share -U alice -m SMB3
# /proc 정보 확인 (클라이언트 측)
$ cat /proc/fs/cifs/Stats
$ cat /proc/fs/cifs/DebugData
encrypt = false로 설정하거나, Wireshark의 Edit → Preferences → Protocols → SMB2 → Session Keys에 세션 키를 등록하면 복호화된 내용을 확인할 수 있습니다.
문제 해결 가이드
자주 발생하는 문제
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| Windows에서 연결 불가 | SMB 버전 불일치 | server min protocol = SMB2로 설정 변경 |
mount error(13): Permission denied | 사용자 미등록 또는 비밀번호 불일치 | ksmbd.adduser -a username |
mount error(112): Host is down | ksmbd.mountd 미실행 | systemctl start ksmbd |
| 파일 쓰기 시 권한 오류 | 서버 디렉터리 권한 불일치 | chown/chmod로 권한 수정, force user 설정 검토 |
| 연결 후 30초에 끊김 | 서명 불일치 | 서버/클라이언트 서명 설정 일치시키기 |
| 느린 디렉터리 목록 | oplock 미활성화 | smb2 leases = yes, oplocks = yes 확인 |
| 대용량 파일 전송 실패 | 버퍼 크기 부족 | smb2 max read/write 증가 |
| dmesg에 오류 반복 | ksmbd 버그 또는 메모리 부족 | 커널 업데이트, debug_type으로 상세 로그 확인 |
# 진단 순서
# 1단계: 모듈 상태 확인
$ lsmod | grep ksmbd
$ systemctl status ksmbd
# 2단계: Netlink 통신 확인
$ ps aux | grep ksmbd.mountd
$ echo 16 > /sys/module/ksmbd/parameters/debug_type # IPC 로그
$ dmesg -w | grep ksmbd
# 3단계: 네트워크 연결 확인
$ ss -tlnp | grep 445
$ nmap -p445 --script smb2-security-mode server_ip
# 4단계: 인증 테스트
$ smbclient //server/share -U testuser -m SMB3
# 5단계: 파일시스템 권한 확인
$ ls -la /srv/smb/share
$ getfacl /srv/smb/share
# 6단계: 상세 패킷 분석
$ tcpdump -i eth0 -s 0 -w /tmp/debug.pcap port 445
SMB2/3 협상 프로토콜 심화
SMB 클라이언트와 서버 간의 프로토콜 협상은 전체 세션의 보안 수준과 기능을 결정하는 핵심 단계입니다. ksmbd는 MS-SMB2 명세를 따르며, 클라이언트가 제시한 dialect 목록에서 서버가 지원하는 가장 높은 버전을 선택합니다.
Dialect 선택 알고리즘
ksmbd는 클라이언트의 Negotiate Request에 포함된 dialect 배열을 내림차순으로 스캔하여 서버가 지원하는 최고 버전을 선택합니다. 이 과정은 ksmbd_lookup_dialect()에서 수행됩니다.
/* Dialect 우선순위 테이블 — smb2ops.c */
static const struct {
__le16 dialect;
int prot_id;
char *name;
} smb2_dialects[] = {
{ cpu_to_le16(0x0311), SMB311_PROT_ID, "3.1.1" },
{ cpu_to_le16(0x0302), SMB302_PROT_ID, "3.0.2" },
{ cpu_to_le16(0x0300), SMB30_PROT_ID, "3.0" },
{ cpu_to_le16(0x0210), SMB21_PROT_ID, "2.1" },
{ cpu_to_le16(0x0202), SMB20_PROT_ID, "2.0" },
};
/* 클라이언트 dialect 목록에서 서버가 지원하는 최고 버전 선택 */
static __le16 ksmbd_lookup_dialect(struct smb2_negotiate_req *req)
{
__le16 *cli_dialects = req->Dialects;
__le16 srv_max = ksmbd_get_server_max_dialect();
/* 서버 최대 → 최소 순으로 매칭 */
for (int i = 0; i < ARRAY_SIZE(smb2_dialects); i++) {
if (smb2_dialects[i].dialect > srv_max)
continue;
for (int j = 0; j < le16_to_cpu(req->DialectCount); j++) {
if (cli_dialects[j] == smb2_dialects[i].dialect)
return smb2_dialects[i].dialect;
}
}
return 0; /* 공통 dialect 없음 → 연결 거부 */
}
Pre-authentication Integrity Hash 동작
SMB 3.1.1에서 도입된 Pre-authentication Integrity Hash는 Negotiate 단계부터 세션 설정 완료까지 모든 메시지를 연쇄 해싱하여 중간자 공격(MITM)을 방지합니다. 이 해시는 세션 키 파생의 입력으로 사용됩니다.
/* Pre-authentication Integrity Hash 계산 — auth.c */
int ksmbd_gen_preauth_integrity_hash(
struct ksmbd_conn *conn,
char *buf,
__u8 *pi_hash)
{
struct crypto_shash *tfm;
struct shash_desc *desc;
int rc;
/* SHA-512 해시 컨텍스트 초기화 */
tfm = crypto_alloc_shash("sha512", 0, 0);
desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm),
GFP_KERNEL);
desc->tfm = tfm;
/* Hash = SHA-512(PreviousHash || CurrentMessage) */
rc = crypto_shash_init(desc);
rc |= crypto_shash_update(desc, pi_hash, 64); /* 이전 해시 */
rc |= crypto_shash_update(desc, buf, get_rfc1002_len(buf) + 4);
rc |= crypto_shash_final(desc, pi_hash); /* 새 해시 출력 */
kfree(desc);
crypto_free_shash(tfm);
return rc;
}
보안 모드 협상
| SecurityMode 플래그 | 비트 | 설명 | ksmbd 처리 |
|---|---|---|---|
| SMB2_NEGOTIATE_SIGNING_ENABLED | 0x01 | 서명 가능 | 클라이언트/서버 모두 설정 가능 |
| SMB2_NEGOTIATE_SIGNING_REQUIRED | 0x02 | 서명 필수 | server signing = required 시 설정 |
server min protocol = SMB3_11을 설정하여 SMB 3.1.1만 허용하는 것이 권장됩니다. ksmbd는 smb2ops.c의 ksmbd_lookup_dialect()에서 서버 최소 버전을 먼저 확인합니다.
Capabilities 협상
Negotiate Response에 포함되는 Capabilities 플래그는 서버의 기능 지원 여부를 알려줍니다.
| Capability | 비트 | 설명 | ksmbd 지원 |
|---|---|---|---|
| SMB2_GLOBAL_CAP_DFS | 0x01 | Distributed File System | 부분 지원 (DFS 루트) |
| SMB2_GLOBAL_CAP_LEASING | 0x02 | SMB2 Lease | 지원 |
| SMB2_GLOBAL_CAP_LARGE_MTU | 0x04 | 멀티크레딧 대용량 전송 | 지원 |
| SMB2_GLOBAL_CAP_MULTI_CHANNEL | 0x08 | 멀티채널 | 지원 |
| SMB2_GLOBAL_CAP_PERSISTENT_HANDLES | 0x10 | Persistent Handle | 지원 |
| SMB2_GLOBAL_CAP_DIRECTORY_LEASING | 0x20 | 디렉터리 Lease | 지원 |
| SMB2_GLOBAL_CAP_ENCRYPTION | 0x40 | SMB3 암호화 | 지원 |
/* Capabilities 설정 — smb2pdu.c */
static void ksmbd_set_negotiate_capabilities(
struct ksmbd_conn *conn,
struct smb2_negotiate_rsp *rsp)
{
__le32 caps = 0;
caps |= SMB2_GLOBAL_CAP_LARGE_MTU;
caps |= SMB2_GLOBAL_CAP_LEASING;
if (conn->dialect >= SMB30_PROT_ID) {
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
caps |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_ENCRYPTION)
caps |= SMB2_GLOBAL_CAP_ENCRYPTION;
caps |= SMB2_GLOBAL_CAP_PERSISTENT_HANDLES;
caps |= SMB2_GLOBAL_CAP_DIRECTORY_LEASING;
}
rsp->Capabilities = cpu_to_le32(caps);
}
AES-CCM/GCM 암호화 심화
SMB3 암호화는 AEAD(Authenticated Encryption with Associated Data) 방식을 사용합니다. AES-CCM(Counter with CBC-MAC)과 AES-GCM(Galois/Counter Mode) 두 가지 알고리즘을 지원하며, ksmbd는 커널 Crypto API의 aead 인터페이스를 통해 구현합니다.
AES-CCM vs AES-GCM 비교
| 속성 | AES-128-CCM | AES-128-GCM | AES-256-GCM |
|---|---|---|---|
| SMB 버전 | SMB 3.0+ | SMB 3.1.1 | SMB 3.1.1 |
| Nonce 크기 | 11바이트 | 12바이트 | 12바이트 |
| MAC 태그 | 16바이트 | 16바이트 | 16바이트 |
| 키 크기 | 128비트 | 128비트 | 256비트 |
| 병렬 처리 | 불가 (직렬) | 가능 | 가능 |
| AES-NI 가속 | 부분적 | 완전 | 완전 |
| 처리량 (AES-NI) | ~1.5 GB/s | ~5 GB/s | ~4.5 GB/s |
| 처리량 (SW) | ~200 MB/s | ~250 MB/s | ~200 MB/s |
| ksmbd Crypto API | ccm(aes) | gcm(aes) | gcm(aes) |
/* SMB3 암호화/복호화 — smb2pdu.c */
static int smb3_aead_op(
struct ksmbd_conn *conn,
struct kvec *iov,
int nvec,
bool encrypt)
{
struct crypto_aead *tfm;
struct aead_request *req;
struct scatterlist *sg;
int rc;
/* 알고리즘 선택 */
if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
else
tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
/* 키 설정 */
crypto_aead_setkey(tfm,
encrypt ? conn->sess->smb3encryptionkey
: conn->sess->smb3decryptionkey,
conn->cipher_type & SMB2_ENCRYPTION_AES256 ? 32 : 16);
crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE); /* 16바이트 MAC */
/* AEAD 요청 구성 */
req = aead_request_alloc(tfm, GFP_KERNEL);
aead_request_set_crypt(req, sg, sg,
encrypt ? plaintext_len : ciphertext_len,
nonce);
aead_request_set_ad(req, SMB2_TRANSFORM_ASSOC_DATA_SIZE); /* 20B AAD */
/* 암호화 또는 복호화 수행 */
rc = encrypt ? crypto_aead_encrypt(req)
: crypto_aead_decrypt(req);
return rc;
}
grep -o aes /proc/cpuinfo | head -1로 CPU 지원 여부를 확인하고, cat /proc/crypto | grep -B2 "gcm(aes)"에서 driver: aesni-gcm 또는 driver: generic-gcm-aesni가 표시되는지 확인하세요.
공유별 암호화 설정
# 글로벌 암호화 (모든 공유에 적용)
[global]
encrypt = true # 모든 공유에 강제 암호화
server min protocol = SMB3 # 암호화 지원 버전만 허용
# 공유별 선택적 암호화
[sensitive]
path = /data/confidential
encrypt = required # 이 공유만 암호화 강제
valid users = @finance
[public]
path = /data/public
encrypt = false # 암호화 비활성화 (성능 우선)
guest ok = yes
read only = yes
# 클라이언트 측 암호화 마운트
$ mount -t cifs //server/sensitive /mnt \
-o seal,vers=3.1.1,username=alice # seal = 암호화 강제
멀티채널 구현 심화
SMB3 멀티채널의 핵심은 하나의 SMB 세션을 여러 TCP 연결에 걸쳐 공유하는 것입니다. 각 채널은 독립적인 TCP 연결이지만, 동일한 세션 ID, 트리 연결, 파일 핸들을 공유합니다.
인터페이스 탐색 (IOCTL QUERY_NETWORK_INTERFACE_INFO)
멀티채널 설정의 첫 단계는 서버의 사용 가능한 네트워크 인터페이스를 탐색하는 것입니다. 클라이언트는 IOCTL 명령으로 서버에 인터페이스 정보를 요청합니다.
/* IOCTL: QUERY_NETWORK_INTERFACE_INFO — smb2pdu.c */
static int fsctl_query_iface_info(struct ksmbd_conn *conn,
struct smb2_ioctl_rsp *rsp)
{
struct net_device *dev;
struct network_interface_info_ioctl_rsp *nii;
int nbytes = 0;
/* 모든 활성 네트워크 인터페이스 열거 */
rcu_read_lock();
for_each_netdev_rcu(&init_net, dev) {
if (dev->type != ARPHRD_ETHER) /* 이더넷만 */
continue;
if (!(dev->flags & IFF_RUNNING)) /* 링크 업 상태만 */
continue;
nii = (struct network_interface_info_ioctl_rsp *)
(rsp->Buffer + nbytes);
/* 인터페이스 속성 채움 */
nii->IfIndex = cpu_to_le32(dev->ifindex);
nii->Capability = 0; /* RSS/RDMA 지원 여부 */
if (dev->features & NETIF_F_RXHASH)
nii->Capability |= RSS_CAPABLE;
/* 링크 속도 (bps) */
nii->LinkSpeed = cpu_to_le64(
(u64)dev->ethtool_ops->get_link_ksettings(dev, &ks)
* 1000000);
/* IPv4/IPv6 주소 */
ksmbd_fill_iface_address(dev, nii);
nbytes += sizeof(*nii);
}
rcu_read_unlock();
return nbytes;
}
세션 바인딩 검증
추가 채널은 기존 세션에 바인딩해야 합니다. 이 과정에서 동일한 사용자 인증과 Pre-authentication Integrity Hash 검증이 수행됩니다.
I/O 분배 정책
| 분배 정책 | 설명 | 적합한 환경 |
|---|---|---|
| Round-Robin | 채널을 순환하며 요청 분배 | 동일 속도 NIC 환경 |
| Weighted Round-Robin | 링크 속도에 비례하여 분배 | 이종 NIC 혼합 (10G + 1G) |
| Least Outstanding | 미완료 요청이 가장 적은 채널에 분배 | 지연 변동이 큰 환경 |
| Hash-based | 파일 ID 기반 해시로 채널 고정 | 순차 I/O 최적화 (순서 보장) |
ksmbd_conn_alive()가 false를 반환하고, 해당 채널만 정리됩니다. 세션은 다른 채널이 하나라도 살아있으면 유지됩니다. 클라이언트는 끊어진 채널을 통해 전송 중이던 요청을 다른 채널로 자동 재전송합니다.
Oplock/Lease 상태 머신
ksmbd의 oplock/lease 관리는 복잡한 상태 전이를 포함합니다. 파일이 열릴 때 oplock이 부여되고, 충돌하는 접근이 발생하면 단계적으로 oplock이 축소(break)됩니다. 이 상태 전이를 이해하는 것이 ksmbd 디버깅의 핵심입니다.
Oplock 부여 알고리즘
/* oplock 부여 결정 — oplock.c */
static int smb_grant_oplock(
struct ksmbd_work *work,
struct ksmbd_file *fp,
int requested_oplock)
{
struct ksmbd_inode *ci = fp->f_ci;
struct oplock_info *existing_oplock;
/* 현재 파일에 기존 oplock이 있는지 확인 */
existing_oplock = opinfo_get_list(ci);
if (!existing_oplock) {
/* 첫 번째 열기: 요청한 수준 그대로 부여 */
return grant_oplock(fp, requested_oplock);
}
/* 기존 oplock 보유자와 충돌 검사 */
if (existing_oplock->level == SMB2_OPLOCK_LEVEL_BATCH ||
existing_oplock->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
/* 기존 보유자에게 Break 요청 */
oplock_break_requested(existing_oplock, fp);
/* Break 완료까지 대기 후 Level II 또는 None 부여 */
if (wait_for_break_ack(existing_oplock) == 0)
return grant_oplock(fp, SMB2_OPLOCK_LEVEL_II);
}
if (existing_oplock->level == SMB2_OPLOCK_LEVEL_II) {
/* 다중 공유 읽기: Level II 부여 가능 */
return grant_oplock(fp, SMB2_OPLOCK_LEVEL_II);
}
return grant_oplock(fp, SMB2_OPLOCK_LEVEL_NONE);
}
/* Lease vs Oplock 우선순위 */
/* Lease Key가 같은 클라이언트 → 기존 Lease 공유 */
/* Lease Key가 다른 클라이언트 → Lease Break 트리거 */
Lease Break 응답 타임아웃 처리
| 상황 | 타임아웃 | ksmbd 동작 |
|---|---|---|
| Oplock Break (SMB2) | 35초 | 강제 oplock 해제, Level None으로 하향 |
| Lease Break (SMB2.1+) | 35초 | 강제 lease 해제, 요청자에게 접근 허용 |
| Directory Lease Break | 35초 | 디렉터리 캐시 무효화 |
| 네트워크 단절 | TCP keepalive | 연결 정리 후 모든 oplock 해제 |
echo 8 > /sys/module/ksmbd/parameters/debug_type으로 oplock 디버그 로그를 활성화하여 경합 패턴을 파악하세요.
내구성 핸들 심화
Durable Handle은 네트워크 일시 단절에 대응하는 핵심 기능입니다. ksmbd는 Durable Handle v1(SMB 2.1), v2(SMB 3.0), Persistent Handle, Resilient Handle을 모두 구현합니다.
Durable Handle 수명 주기
/* Durable Handle v2 생성 컨텍스트 처리 — smb2pdu.c */
static int parse_durable_v2_context(
struct ksmbd_work *work,
struct smb2_create_req *req,
struct ksmbd_file *fp)
{
struct create_durable_v2_req *dv2;
u32 timeout;
dv2 = smb2_find_context_vals(req,
SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2);
if (!dv2)
return 0;
/* CreateGuid 저장 (재연결 시 식별에 사용) */
memcpy(fp->create_guid, dv2->CreateGuid, 16);
/* 타임아웃 설정 (밀리초) */
timeout = le32_to_cpu(dv2->Timeout);
if (timeout == 0)
timeout = 60000; /* 기본: 60초 */
fp->durable_timeout = jiffies + msecs_to_jiffies(timeout);
/* Persistent 핸들 확인 */
if (dv2->Flags & SMB2_DHANDLE_FLAG_PERSISTENT) {
/* CA(Continuously Available) 공유에서만 허용 */
if (work->tcon->share_conf->flags & KSMBD_SHARE_FLAG_CA)
fp->is_persistent = true;
else
return -EINVAL;
}
fp->is_durable = true;
/* durable 목록에 등록 (scavenger가 정리) */
list_add(&fp->durable_node, &durable_handle_list);
return 0;
}
/* Durable Handle 재연결 (Reconnect) */
static struct ksmbd_file *ksmbd_lookup_durable_fd(
__u8 *create_guid)
{
struct ksmbd_file *fp;
spin_lock(&durable_lock);
list_for_each_entry(fp, &durable_handle_list, durable_node) {
if (memcmp(fp->create_guid, create_guid, 16) == 0) {
/* CreateGuid 일치 → 핸들 반환 */
atomic_inc(&fp->refcount);
spin_unlock(&durable_lock);
return fp;
}
}
spin_unlock(&durable_lock);
return NULL;
}
워커 스레드풀 아키텍처 심화
ksmbd의 워크큐 아키텍처는 커널의 CMWQ(Concurrency Managed Workqueue) 프레임워크 위에 구축됩니다. 각 SMB 요청은 ksmbd_work 구조체로 래핑되어 글로벌 워크큐에 제출되며, 커널이 CPU 가용성에 따라 워커 스레드를 동적으로 관리합니다.
요청 처리 상세 흐름
/* 요청 처리 메인 핸들러 — ksmbd_work.c */
static void handle_ksmbd_work(struct work_struct *wk)
{
struct ksmbd_work *work = container_of(wk, struct ksmbd_work, work);
struct ksmbd_conn *conn = work->conn;
int rc;
/* Phase 1: 암호화된 패킷이면 복호화 */
if (ksmbd_pdu_is_encrypted(work->request_buf)) {
rc = smb3_decrypt_req(work);
if (rc) {
conn->status = KSMBD_SESS_NEED_RECONNECT;
goto out;
}
}
/* Phase 2: SMB2 헤더 유효성 검증 */
rc = ksmbd_smb2_check_message(work);
if (rc) {
conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);
goto send;
}
/* Phase 3: 크레딧 확인 및 차감 */
rc = smb2_check_credit_charge(conn, work);
if (rc)
goto send;
/* Phase 4: Compound Request 처리 루프 */
do {
/* 명령별 핸들러 호출 */
rc = conn->cmds[get_command(work)].proc(work);
/* Compound: 다음 요청으로 이동 */
} while (is_compound_request(work));
send:
/* Phase 5: 서명 적용 (필요 시) */
if (conn->signing_required)
ksmbd_sign_smb2_pdu(conn, work->sess->smb3signingkey,
work->iov, work->iov_cnt, work->sig);
/* Phase 6: 암호화 적용 (필요 시) */
if (conn->encryption_required)
smb3_encrypt_resp(work);
/* Phase 7: TCP/RDMA로 응답 전송 */
conn->transport->ops->writev(conn->transport, work->iov, work->iov_cnt);
out:
/* 크레딧 반환 및 work 해제 */
smb2_set_credits(work, conn);
ksmbd_free_work_struct(work);
}
크레딧 관리 메커니즘
SMB2 크레딧 시스템은 클라이언트가 동시에 보낼 수 있는 요청의 수를 제어합니다. 서버는 응답에 크레딧을 부여/회수하여 서버 리소스를 보호합니다.
| 크레딧 파라미터 | 설명 | ksmbd 기본값 |
|---|---|---|
| 초기 크레딧 | Negotiate 후 클라이언트에 부여 | 1 |
| 최대 크레딧 | 단일 연결당 최대 동시 요청 | 8192 |
| CreditCharge | 대용량 요청의 크레딧 소비량 | (PayloadSize / 65536) + 1 |
| CreditRequest | 클라이언트가 추가 요청하는 크레딧 수 | 요청별 다름 |
| CreditResponse | 서버가 부여하는 크레딧 수 | 동적 (서버 부하 기반) |
/* 크레딧 관리 — smb2misc.c */
int smb2_set_credits(
struct ksmbd_work *work,
struct ksmbd_conn *conn)
{
struct smb2_hdr *rsp_hdr = smb2_get_msg(work->response_buf);
u16 credits_requested = le16_to_cpu(rsp_hdr->CreditRequest);
u16 credits_granted;
/* 동적 크레딧 부여 알고리즘 */
if (atomic_read(&conn->req_running) > conn->max_credits / 2) {
/* 서버 부하 높음 → 크레딧 절반만 부여 */
credits_granted = credits_requested / 2;
} else {
/* 서버 여유 → 요청한 만큼 부여 */
credits_granted = credits_requested;
}
/* 최대 한도 초과 방지 */
if (atomic_read(&conn->total_credits) + credits_granted
> conn->max_credits)
credits_granted = conn->max_credits
- atomic_read(&conn->total_credits);
rsp_hdr->CreditRequest = cpu_to_le16(credits_granted);
atomic_add(credits_granted, &conn->total_credits);
return 0;
}
cat /sys/kernel/debug/workqueue/ksmbd_io_wq(커널 디버그 파일시스템 마운트 필요)를 사용합니다. 워커 스레드 수가 지속적으로 증가하면 서버 과부하 상태입니다. cat /proc/stat | grep "procs_running"과 함께 확인하여 CPU 병목인지 I/O 병목인지 구분하세요.
컴파운드 요청 처리 심화
Compound Request는 SMB 성능 최적화의 핵심입니다. Related Compound는 이전 명령의 결과를 다음 명령이 참조하므로 서버 측에서 원자적 처리가 가능하며, Unrelated Compound는 독립적인 요청을 묶어 네트워크 왕복을 절약합니다.
패킷 구조 (Wire Format)
Windows 클라이언트의 Compound 활용 패턴
| 사용 패턴 | Compound 구성 | 용도 |
|---|---|---|
| 파일 읽기 | CREATE + READ + CLOSE | Windows Explorer에서 파일 미리보기 |
| 파일 속성 조회 | CREATE + QUERY_INFO + CLOSE | 파일 속성 대화 상자 |
| 파일 쓰기 | CREATE + WRITE + FLUSH + CLOSE | Office 문서 저장 |
| 디렉터리 목록 | CREATE + QUERY_DIRECTORY + CLOSE | 폴더 탐색 |
| 파일 이동 | CREATE + SET_INFO(rename) + CLOSE | 파일 이름 변경 |
| 보안 조회 | CREATE + QUERY_INFO(security) + CLOSE | 파일 권한 확인 |
smb2.header.next_command 필드가 0이 아닌 패킷을 필터링하면 compound 요청만 확인할 수 있습니다. smb2.flags.related 필터로 related/unrelated를 구분할 수 있습니다.
변경 알림 (Change Notify) 심화
Change Notify는 SMB의 비동기 명령 중 하나로, 서버가 디렉터리 변경을 감지할 때까지 응답을 지연합니다. ksmbd는 커널의 fsnotify 인프라를 활용하여 파일시스템 이벤트를 효율적으로 모니터링합니다.
fsnotify 통합 구조
/* ksmbd Change Notify fsnotify 통합 */
/* fsnotify 이벤트 마스크 매핑 */
static __u32 smb_to_fsnotify_mask(__le32 completion_filter)
{
__u32 mask = 0;
__le32 f = le32_to_cpu(completion_filter);
if (f & FILE_NOTIFY_CHANGE_FILE_NAME)
mask |= FS_CREATE | FS_DELETE | FS_MOVED_FROM | FS_MOVED_TO;
if (f & FILE_NOTIFY_CHANGE_DIR_NAME)
mask |= FS_CREATE | FS_DELETE | FS_ISDIR;
if (f & FILE_NOTIFY_CHANGE_SIZE)
mask |= FS_MODIFY;
if (f & FILE_NOTIFY_CHANGE_LAST_WRITE)
mask |= FS_MODIFY;
if (f & FILE_NOTIFY_CHANGE_ATTRIBUTES)
mask |= FS_ATTRIB;
if (f & FILE_NOTIFY_CHANGE_SECURITY)
mask |= FS_ATTRIB;
return mask;
}
/* SMB Action ↔ fsnotify 이벤트 변환 */
static __le32 fsnotify_to_smb_action(__u32 event)
{
if (event & FS_CREATE)
return cpu_to_le32(FILE_ACTION_ADDED);
if (event & FS_DELETE)
return cpu_to_le32(FILE_ACTION_REMOVED);
if (event & FS_MODIFY)
return cpu_to_le32(FILE_ACTION_MODIFIED);
if (event & (FS_MOVED_FROM | FS_MOVED_TO))
return cpu_to_le32(FILE_ACTION_RENAMED_NEW_NAME);
return 0;
}
Change Notify 응답 형식
| 필드 | 크기 | 설명 |
|---|---|---|
| NextEntryOffset | 4바이트 | 다음 변경 항목까지 오프셋 (0이면 마지막) |
| Action | 4바이트 | FILE_ACTION_ADDED/REMOVED/MODIFIED/... |
| FileNameLength | 4바이트 | 파일 이름 바이트 수 (UTF-16LE) |
| FileName | 가변 | 변경된 파일/디렉터리 이름 (UTF-16LE) |
SMB2_WATCH_TREE)는 상당한 커널 메모리를 소비합니다. 각 감시 대상 디렉터리마다 fsnotify 마크가 생성되기 때문입니다. 많은 수의 Windows Explorer 창이 열린 환경에서는 메모리 사용량을 모니터링하세요. cat /proc/sys/fs/inotify/max_user_watches로 시스템 전체 감시 한도를 확인할 수 있습니다.
비동기 응답 처리
Change Notify는 SMB2의 비동기 명령 모델을 사용합니다. 서버는 즉시 STATUS_PENDING INTERIM 응답을 보내고, 실제 변경이 발생했을 때 비동기 응답을 전송합니다.
/* 비동기 응답 처리 — smb2pdu.c */
static int smb2_send_interim_resp(
struct ksmbd_work *work,
__le32 nt_status)
{
struct smb2_hdr *rsp_hdr = smb2_get_msg(work->response_buf);
/* 비동기 플래그 설정 */
rsp_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
rsp_hdr->Status = nt_status; /* STATUS_PENDING */
/* 비동기 ID 할당 (나중에 응답 매칭에 사용) */
rsp_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
/* INTERIM 응답 즉시 전송 */
work->conn->transport->ops->writev(
work->conn->transport,
work->iov, work->iov_cnt);
/* work는 대기 목록에 유지 (실제 응답 시까지) */
list_add_tail(&work->async_request_entry,
&work->conn->async_requests);
return 0;
}
Kerberos/NTLM 인증 심화
ksmbd의 인증 아키텍처는 커널에서 SPNEGO 토큰 파싱과 챌린지-응답 검증을 수행하되, 패스워드 해시 조회와 Kerberos 티켓 검증은 사용자 공간 ksmbd.mountd에 위임하는 분할 모델입니다.
SPNEGO 토큰 처리
NTLMv2 세션 보안 레벨
| 보안 레벨 | 설명 | 해시 알고리즘 | ksmbd 지원 |
|---|---|---|---|
| LM Response | 레거시 LAN Manager 해시 (매우 취약) | DES(password → 14자 패딩) | 미지원 |
| NTLM Response | NT Hash 기반 (NTLMv1) | MD4(password) + DES | 미지원 |
| NTLMv2 Response | HMACv2 기반 (권장) | HMAC-MD5(NTOWFv2, Challenge + Blob) | 지원 (기본) |
| NTLMv2 + Session Security | NTLMv2 + 세션 서명/실링 | HMAC-MD5 + RC4(세션 키) | 지원 |
/* NTLMv2 인증 검증 — auth.c */
int ksmbd_auth_ntlmv2(
struct ksmbd_session *sess,
struct ntlmv2_resp *ntlmv2,
int blen,
char *challenge)
{
char ntlmv2_hash[CIFS_ENCPWD_SIZE];
char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
int rc;
/* 1. NTOWFv2 계산 */
/* = HMAC_MD5(MD4(Password), UPPER(User) || Domain) */
rc = ksmbd_gen_ntlmv2_hash(sess,
sess->user->passkey, ntlmv2_hash);
/* 2. 예상 NTProofStr 계산 */
/* = HMAC_MD5(NTOWFv2, ServerChallenge || ClientBlob) */
rc = ksmbd_hmac_md5(ntlmv2_hash, CIFS_ENCPWD_SIZE,
challenge, 8, /* 8바이트 서버 챌린지 */
ntlmv2->blob_buf, blen, /* 클라이언트 블롭 */
ntlmv2_rsp);
/* 3. 클라이언트의 NTProofStr과 비교 */
if (memcmp(ntlmv2->ntlmv2_hash, ntlmv2_rsp,
CIFS_HMAC_MD5_HASH_SIZE) != 0) {
pr_err("ksmbd: NTLMv2 authentication failed for %s\n",
sess->user->name);
return -EACCES;
}
/* 4. 세션 키 파생 */
/* SessionBaseKey = HMAC_MD5(NTOWFv2, NTProofStr) */
rc = ksmbd_hmac_md5(ntlmv2_hash, CIFS_ENCPWD_SIZE,
ntlmv2->ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE,
NULL, 0,
sess->sess_key);
return 0;
}
server signing = required를 설정하여 모든 SMB 패킷에 서명을 강제하면 릴레이 공격을 방지할 수 있습니다. SMB 3.1.1의 Pre-authentication Integrity Hash를 사용하면 더 강력한 보호가 가능합니다. 가능하면 Kerberos 인증을 사용하는 것이 가장 안전합니다.
ACL 매핑 (POSIX ↔ Windows) 심화
Windows와 POSIX의 접근 제어 모델은 근본적으로 다르며, ksmbd는 smbacl.c에서 이 두 모델 간의 최선의 변환을 수행합니다. 변환 과정에서 의미가 완전히 보존되지 않는 경우가 있어 주의가 필요합니다.
변환 규칙 상세
| Windows ACE | POSIX 매핑 | 역방향 손실 |
|---|---|---|
| ALLOW Owner (FULL_CONTROL) | user::rwx | 없음 |
| ALLOW Group (READ+EXECUTE) | group::r-x | 없음 |
| ALLOW Everyone (READ) | other::r-- | 없음 |
| DENY specific user | 변환 불가 (POSIX ACL에 DENY 없음) | 정보 손실 |
| ALLOW user:alice (RW) | user:alice:rw- (확장 ACL) | 없음 (확장 ACL 지원 시) |
| INHERIT_ONLY ACE | default ACL | 일부 상속 플래그 손실 |
| GENERIC_ALL + DELETE + WRITE_DAC | rwx (7) | 세밀한 권한 손실 |
SID ↔ UID/GID 매핑
/* SID → UID 변환 — smbacl.c */
uid_t sid_to_uid(struct smb_sid *sid)
{
/* Well-Known SID 매핑 */
if (sid_equal(sid, &sid_nt_authority_system))
return 0; /* NT AUTHORITY\SYSTEM → root */
if (sid_equal(sid, &sid_creator_owner))
return 0; /* CREATOR OWNER → 파일 소유자 */
/* 도메인 SID: RID를 UID로 매핑 */
/* S-1-5-21-{domain}-{RID} → UID = RID */
if (sid->authority[5] == 5 &&
sid->sub_auth_count >= 4) {
return le32_to_cpu(sid->sub_auth[sid->sub_auth_count - 1]);
}
/* UNIX SID: S-1-22-1-{UID} → UID 직접 사용 */
if (sid->authority[5] == 22 &&
sid->sub_auth[0] == cpu_to_le32(1)) {
return le32_to_cpu(sid->sub_auth[1]);
}
return INVALID_UID;
}
/* UID → SID 변환 */
void uid_to_sid(uid_t uid, struct smb_sid *sid)
{
if (uid == 0) {
/* root → S-1-5-18 (NT AUTHORITY\SYSTEM) */
*sid = sid_nt_authority_system;
} else {
/* 일반 사용자 → S-1-22-1-{UID} (UNIX SID) */
sid->revision = 1;
sid->authority[5] = 22;
sid->sub_auth_count = 2;
sid->sub_auth[0] = cpu_to_le32(1); /* 1 = UID */
sid->sub_auth[1] = cpu_to_le32(uid);
}
}
xattr 기반 ACL 저장
| xattr 이름 | 설명 | 저장 형식 |
|---|---|---|
system.posix_acl_access | 접근 ACL | POSIX ACL 바이너리 |
system.posix_acl_default | 기본(상속) ACL | POSIX ACL 바이너리 |
security.NTACL | Windows NT Security Descriptor 캐시 | NDR 인코딩된 NTSD |
user.DOSATTRIB | DOS 파일 속성 (읽기전용, 숨김 등) | 텍스트 ("0x0020") |
user.DOSSTREAM.* | Alternate Data Stream | 스트림 데이터 |
inherit acls = yes를 smb.conf에 설정하면 부모 디렉터리의 POSIX default ACL이 자식 파일에 적용됩니다. 그러나 POSIX default ACL은 Windows의 세밀한 상속 규칙(OBJECT_INHERIT, CONTAINER_INHERIT, INHERIT_ONLY)을 완전히 재현하지 못합니다.
성능 튜닝 심화
ksmbd의 성능 최적화는 커널 파라미터, 파일시스템 옵션, 네트워크 설정, SMB 프로토콜 옵션의 조합으로 이루어집니다. 워크로드 특성에 따라 적절한 튜닝 전략이 달라집니다.
병목 지점 분석
성능 튜닝 체크리스트
| 계층 | 파라미터 | 기본값 | 권장값 | 효과 |
|---|---|---|---|---|
| SMB | smb2 max read/write | 1MB | 8MB | 순차 I/O 처리량 향상 |
| SMB | smb2 leases | yes | yes | 클라이언트 캐싱 활성화 |
| SMB | server multi channel support | no | yes (다수 NIC) | 대역폭 집계 |
| SMB | durable handle | yes | yes | 재연결 시 핸들 보존 |
| TCP | net.core.rmem_max | 212992 | 16777216 | 대용량 수신 버퍼 |
| TCP | net.core.wmem_max | 212992 | 16777216 | 대용량 전송 버퍼 |
| TCP | net.ipv4.tcp_rmem | 4096 131072 6291456 | 4096 87380 16777216 | TCP 자동 튜닝 범위 확대 |
| TCP | net.core.netdev_max_backlog | 1000 | 5000 | 고속 NIC 패킷 큐 |
| FS | mount option: noatime | 미적용 | noatime | 읽기 시 atime 갱신 불필요 |
| FS | mount option: data=writeback | data=ordered | writeback (ext4) | 쓰기 성능 향상 (안전성 절충) |
| 시스템 | vm.dirty_ratio | 20 | 40 | 쓰기 캐시 비율 증가 |
| 시스템 | vm.dirty_background_ratio | 10 | 5 | 조기 비동기 플러시 |
성능 모니터링 명령
# SMB 명령별 레이턴시 측정 (bpftrace)
$ bpftrace -e '
kprobe:smb2_read { @read_start[tid] = nsecs; }
kretprobe:smb2_read /@read_start[tid]/ {
@read_latency_us = hist((nsecs - @read_start[tid]) / 1000);
delete(@read_start[tid]);
}
kprobe:smb2_write { @write_start[tid] = nsecs; }
kretprobe:smb2_write /@write_start[tid]/ {
@write_latency_us = hist((nsecs - @write_start[tid]) / 1000);
delete(@write_start[tid]);
}
interval:s:10 { print(@read_latency_us); print(@write_latency_us); }'
# ksmbd 연결당 처리량 모니터링
$ bpftrace -e '
kprobe:ksmbd_vfs_read { @read_bytes += arg2; }
kprobe:ksmbd_vfs_write { @write_bytes += arg2; }
interval:s:5 {
printf("READ: %d MB/s, WRITE: %d MB/s\n",
@read_bytes / 1048576 / 5,
@write_bytes / 1048576 / 5);
@read_bytes = 0; @write_bytes = 0;
}'
# oplock break 빈도 추적
$ bpftrace -e '
kprobe:smb2_send_oplock_break {
@oplock_breaks = count();
}
interval:s:10 { print(@oplock_breaks); clear(@oplock_breaks); }'
# 워크큐 대기 시간 측정
$ bpftrace -e '
kprobe:ksmbd_queue_work { @queue_time[tid] = nsecs; }
kprobe:handle_ksmbd_work /@queue_time[tid]/ {
@queue_wait_us = hist((nsecs - @queue_time[tid]) / 1000);
delete(@queue_time[tid]);
}'
# 전체 시스템 성능 프로파일링
$ perf record -g -a -F 99 -- sleep 30
$ perf report --no-children --sort=dso,symbol | grep ksmbd
# fio를 이용한 종합 벤치마크 스크립트
$ for bs in 4k 64k 1M; do
for rw in read write randread randwrite; do
fio --name=smb_${rw}_${bs} \
--filename=/mnt/smb/testfile \
--rw=$rw --bs=$bs --size=2G \
--numjobs=4 --iodepth=32 \
--runtime=60 --time_based \
--group_reporting \
--output=results_${rw}_${bs}.json \
--output-format=json
done
done
CONFIG_SMB_SERVER_SMBDIRECT=y로 빌드하여 SMB Direct를 활성화하세요. RDMA는 CPU 개입 없이 NIC 간 직접 메모리 전송을 수행하므로, 100 Gbps 이상의 환경에서 TCP 대비 40~60% 높은 처리량과 50% 낮은 CPU 사용률을 달성합니다. 클라이언트에서는 rdma 마운트 옵션을 사용합니다.
워크로드별 최적 설정 프로파일
| 워크로드 | smb2 max read/write | cache 옵션 | 멀티채널 | 암호화 | 추가 권장 사항 |
|---|---|---|---|---|---|
| 대용량 영상 편집 | 8MB | loose | yes (4채널) | off | Jumbo Frame 9000, RDMA 권장 |
| Office 문서 협업 | 1MB | strict | yes | on | Lease 활성화, oplock thrashing 주의 |
| 데이터베이스 백업 | 8MB | none | yes | on | WriteThrough, data=journal |
| 소프트웨어 빌드 | 4MB | strict | yes | off | noatime, Compound 활용 |
| 웹 서버 정적 콘텐츠 | 4MB | loose | no | off | read only, 페이지 캐시 극대화 |
| 가상 머신 스토리지 | 8MB | none | yes (2채널) | off | Direct I/O, O_DIRECT |
NUMA 환경 최적화
# NUMA 토폴로지 확인
$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0-15
node 1 cpus: 16-31
node distances:
node 0 1
0: 10 21
1: 21 10
# NIC가 연결된 NUMA 노드 확인
$ cat /sys/class/net/eth0/device/numa_node
0
# ksmbd 워커가 NIC와 같은 NUMA 노드에서 실행되도록 IRQ 친화도 설정
# eth0의 IRQ를 NUMA 노드 0(CPU 0-15)에 고정
$ for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':'); do
echo 0000ffff > /proc/irq/$irq/smp_affinity
done
# 공유 디렉터리의 스토리지가 NUMA 노드 0에 연결되어 있다면
# 메모리 할당도 같은 노드에서 수행하도록 설정
$ numactl --cpunodebind=0 --membind=0 ksmbd.mountd
# NUMA 메모리 접근 통계 확인
$ numastat -p $(pgrep ksmbd.mountd)
# perf로 NUMA 원격 메모리 접근 비율 확인
$ perf stat -e 'node-loads,node-load-misses' -a -- sleep 10
irqbalance 대신 수동으로 IRQ 친화도를 설정하고, 스토리지와 같은 NUMA 노드의 NIC를 우선 사용하도록 멀티채널 설정을 조정하세요. lstopo(hwloc 패키지) 명령으로 NUMA 토폴로지를 시각적으로 확인할 수 있습니다.
관련 문서
ksmbd와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.