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 대비 구조적 차이와 배포 시 튜닝/보안 체크포인트를 상세히 정리합니다.

전제 조건: VFS, 네트워크 스택, NFS 문서를 먼저 읽으세요. ksmbd는 VFS 위에서 동작하는 네트워크 파일 서버입니다. 파일 잠금, 캐시 일관성, 네트워크 소켓 처리를 이해해야 ksmbd 내부 구조를 파악할 수 있습니다.
일상 비유: ksmbd는 커널 안에 내장된 파일 공유 창구와 같습니다. 기존 Samba는 "건물 밖에 있는 별도 안내소"처럼 매번 건물(커널) 안팎을 오가야 했지만, ksmbd는 건물 1층 로비에 창구를 두어 고객(SMB 클라이언트)이 바로 서비스를 받습니다. 보안 심사(인증)만 외부 전문 업체(ksmbd-tools)에 위탁하고, 파일 열람·복사·수정은 모두 건물 내부에서 직접 처리합니다.

핵심 요약

  • 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)으로 구성됩니다.

단계별 이해

  1. ksmbd 활성화 확인lsmod | grep ksmbd로 커널 모듈이 로드되었는지 확인합니다.

    Linux 5.15 이상에서 CONFIG_SMB_SERVER=m으로 빌드되어 있어야 합니다.

  2. ksmbd-tools 설치 및 설정/etc/ksmbd/smb.conf에 공유 디렉터리를 정의하고 ksmbd.adduser로 사용자를 추가합니다.

    Samba의 smb.conf와 문법이 유사하지만 지원 옵션이 다릅니다.

  3. 커널 아키텍처 이해 — TCP 연결 수신 → SMB 헤더 파싱 → VFS 호출 → 응답 전송의 흐름을 파악합니다.

    인증/계정 조회만 Netlink으로 사용자 공간에 위임합니다.

  4. oplock/lease 동작 이해 — 클라이언트가 파일을 열면 exclusive oplock을 획득합니다. 다른 클라이언트가 같은 파일을 열면 서버가 lease break를 보냅니다.

    클라이언트는 캐시를 플러시하고 oplock을 반납한 후 다른 클라이언트가 접근할 수 있습니다.

  5. SMB3 암호화 적용smb.conf에서 encrypt = true를 설정하면 SMB3 AES-GCM으로 모든 데이터가 암호화됩니다.

    클라이언트에서 seal 마운트 옵션과 함께 사용하세요.

  6. 성능 벤치마크smbclientfio로 처리량을 측정합니다. 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
KconfigCONFIG_SMB_SERVER
의존 모듈CONFIG_CRYPTO, CONFIG_NLATTR, CONFIG_INET
RDMA 지원CONFIG_SMB_SERVER_SMBDIRECT (SMB Direct over RDMA)
라이선스GPLv2
설계 원칙: ksmbd는 "최소 커널 코드" 원칙을 따릅니다. 파일 I/O 경로(성능에 직결되는 hot path)만 커널에 두고, 보안 민감한 인증/계정 관리와 복잡한 설정 파싱은 사용자 공간 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/CIFSWindows NT기본 파일 공유 (보안 취약)미지원
SMB2.0Vista/2008파이프라이닝, compound 요청지원
SMB2.1Windows 7Client Oplock Lease지원
SMB3.0Windows 8암호화(AES-CCM), 멀티채널, RDMA지원
SMB3.0.2Windows 8.1Durable Handle v2, 개선된 Lease지원
SMB3.1.1Windows 10Pre-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 서명 */
};
SMB2 Transform Header: SMB3 암호화가 활성화되면 일반 SMB2 헤더 앞에 52바이트 Transform Header(0xFD534D42)가 추가됩니다. 이 헤더에는 AES nonce, 원본 메시지 크기, 암호화 플래그가 포함되어 있으며, 뒤따르는 페이로드 전체가 AES-CCM/GCM으로 암호화됩니다.

주요 SMB2 명령

명령코드기능ksmbd 핸들러
NEGOTIATE0x0000프로토콜 버전/암호화 협상smb2_negotiate()
SESSION_SETUP0x0001인증 (NTLM/Kerberos)smb2_sess_setup()
LOGOFF0x0002세션 종료smb2_session_logoff()
TREE_CONNECT0x0003공유 폴더 연결smb2_tree_connect()
TREE_DISCONNECT0x0004공유 연결 해제smb2_tree_disconnect()
CREATE0x0005파일/디렉터리 열기/생성smb2_open()
CLOSE0x0006파일 핸들 닫기smb2_close()
FLUSH0x0007버퍼 플러시smb2_flush()
READ0x0008파일 읽기smb2_read()
WRITE0x0009파일 쓰기smb2_write()
LOCK0x000A바이트 범위 잠금smb2_lock()
IOCTL0x000B파일시스템 제어 (DFS, Copychunk)smb2_ioctl()
CANCEL0x000C비동기 요청 취소smb2_cancel()
ECHO0x000D연결 유효성 확인smb2_echo()
QUERY_DIRECTORY0x000E디렉터리 목록smb2_query_dir()
CHANGE_NOTIFY0x000F디렉터리 변경 감시smb2_notify()
QUERY_INFO0x0010파일/공유/보안 정보 조회smb2_query_info()
SET_INFO0x0011파일/보안 정보 설정smb2_set_info()
OPLOCK_BREAK0x0012Oplock/Lease Break 알림smb2_oplock_break()

ksmbd 아키텍처

Windows/macOS/Linux SMB 클라이언트 TCP 445 네트워크 연결 ksmbd.ko (커널 공간) SMB 파서 세션 관리 oplock/lease 암호화/서명 Netlink IPC → ksmbd-tools (인증·사용자 조회) ksmbd-tools (사용자 공간) ksmbd.mountd ksmbdpwd.db smb.conf VFS (Virtual Filesystem Switch) ext4 / XFS / Btrfs / NFS (실제 스토리지)

모듈 구성 요소

ksmbd.ko는 내부적으로 여러 서브시스템으로 나뉘어 있으며, 각각 독립적인 역할을 수행합니다.

전송 계층 (Transport Layer) transport_tcp.c transport_rdma.c transport_ipc.c (Netlink) connection.c (연결 관리) SMB 프로토콜 엔진 smb2pdu.c smb2ops.c smb2misc.c smb_common.c ndr.c (직렬화) 보안 모듈 auth.c crypto_ctx.c smbacl.c 리소스 관리 (mgmt/) user_session.c tree_connect.c share_config.c VFS 인터페이스 vfs.c (read/write/lock) vfs_cache.c (핸들 캐시) oplock.c (oplock/lease) misc.c (유틸리티) 서버 코어 server.c (모듈 진입점) ksmbd_work.c (워크큐) glob.h (전역 정의) unicode.c (문자셋 변환)

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를 할당하여 글로벌 워크큐에 제출합니다.

TCP 리스너 ksmbd_listener socket accept() 포트 445 대기 연결 스레드 (per-conn) ksmbd:conn 커널 스레드 SMB 패킷 수신 + 디코딩 ksmbd_work 할당 ksmbd_io_wq (워크큐) WQ_MEM_RECLAIM | WQ_UNBOUND max_active = 동적 (CPU 수 기반) handle_ksmbd_work() 실행 handle_ksmbd_work() 처리 흐름 1. 헤더 검증 서명/크레딧 확인 2. 명령 디스패치 cmds[cmd] 호출 3. VFS 처리 파일 I/O 수행 4. 응답 전송 TCP/RDMA 기록 비동기 요청 처리 CHANGE_NOTIFY, OPLOCK_BREAK: INTERIM 응답 후 대기 크레딧 제어 CreditCharge/CreditRequest로 동시 요청 수 제한 Compound Request 처리 NextCommand != 0이면 같은 ksmbd_work 내에서 순차 처리 (CREATE+READ+CLOSE 한 번에)
/* 워크큐 생성 — 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);
    }
}
성능 특성: ksmbd는 WQ_UNBOUND 워크큐를 사용하므로 워커 스레드가 특정 CPU에 고정되지 않습니다. 이는 NUMA 시스템에서 메모리 접근 패턴이 균일하지 않을 수 있지만, 소규모 NAS 환경에서는 CPU 활용률을 극대화합니다. 대규모 환경에서는 irqbalance와 함께 NIC IRQ를 특정 NUMA 노드에 고정하면 성능이 향상됩니다.

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,
};
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]
/* 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]; /* 공유 경로 */
};
ksmbd.mountd 장애 시: ksmbd.mountd가 중단되면 새로운 로그인과 공유 연결이 불가능합니다. 기존에 인증된 세션의 파일 I/O는 계속 동작하지만, 새 세션을 수립할 수 없습니다. systemdRestart=always를 설정하여 자동 복구하는 것이 권장됩니다.

SMB2/3 Negotiate 협상

SMB 세션은 Negotiate 단계에서 시작합니다. 클라이언트가 지원하는 프로토콜 버전, 보안 기능, 암호화 알고리즘 목록을 서버에 전송하면, 서버는 가장 높은 공통 버전을 선택하여 응답합니다.

SMB 클라이언트 ksmbd (서버) NEGOTIATE Request Dialects: [0x0202, 0x0210, 0x0300, 0x0311] SecurityMode: Signing enabled Negotiate Contexts: PREAUTH_INTEGRITY, ENCRYPTION smb2_negotiate() 최고 공통 dialect 선택 GUID/보안 모드 결정 NEGOTIATE Response DialectRevision: 0x0311 (SMB 3.1.1) SecurityMode: Signing required MaxTransactSize: 8MB, MaxReadSize: 8MB, MaxWriteSize: 8MB NegotiateContext: PREAUTH(SHA-512), ENCRYPT(AES-128-GCM) SMB 3.1.1 Pre-authentication Integrity Hash PreauthIntegrityHash = SHA-512(PreauthIntegrityHash || NegotiateRequest || NegotiateResponse) SESSION_SETUP (SPNEGO + NTLMSSP_NEGOTIATE) SESSION_SETUP (NTLMSSP_CHALLENGE) SESSION_SETUP (NTLMSSP_AUTH) Netlink → ksmbd.mountd SESSION_SETUP Response (STATUS_SUCCESS) 세션 키 파생 → 서명/암호화 키 생성

Negotiate Context 상세

SMB 3.1.1에서 도입된 Negotiate Context는 프로토콜 협상 시 추가 보안 옵션을 교환하는 구조체입니다.

Context 타입ID설명
PREAUTH_INTEGRITY_CAPABILITIES0x0001Pre-authentication 무결성 해시 알고리즘 (SHA-512)
ENCRYPTION_CAPABILITIES0x0002암호화 알고리즘 (AES-128-CCM, AES-128-GCM, AES-256-CCM, AES-256-GCM)
COMPRESSION_CAPABILITIES0x0003압축 알고리즘 (LZ77, LZ77+Huffman, LZNT1, Pattern_V1)
NETNAME_NEGOTIATE_CONTEXT0x0005클라이언트가 연결하는 서버 이름
SIGNING_CAPABILITIES0x0008서명 알고리즘 (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에 위임합니다.

클라이언트 ksmbd.ko (auth.c) ksmbd.mountd 1. NTLMSSP_NEGOTIATE [NTLM Flags, Domain, Workstation] Challenge 생성 8바이트 랜덤 값 2. NTLMSSP_CHALLENGE [Challenge, TargetInfo, Timestamp] NTProofStr 계산 HMAC-MD5(NT Hash) 3. NTLMSSP_AUTH [NtChallengeResponse, UserName, Domain] 4. LOGIN_REQUEST [username] 5. LOGIN_RESPONSE [NT Hash, UID, GID] NTProofStr 검증 세션 키 파생

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 객체 생성
READvfs_read(), kernel_read()파일 내용 읽기
WRITEvfs_write(), kernel_write()파일 내용 쓰기
QUERY_INFOvfs_getattr()파일 속성(크기, 시간, 권한) 조회
SET_INFOvfs_setattr(), vfs_utimes()파일 속성 변경
QUERY_DIRECTORYiterate_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에서 처리합니다.

Windows NT Security Descriptor Owner SID (S-1-5-21-...) Group SID (S-1-5-21-...-513) DACL (Discretionary ACL) ACE 1: ALLOW Owner (RWX) ACE 2: ALLOW Group (RX) ACE 3: ALLOW Everyone (R) ACE 4: DENY Guest (--) GENERIC_READ, FILE_WRITE_DATA, ... POSIX ACL + mode bits UID (uid_to_sid() 변환) GID (gid_to_sid() 변환) POSIX ACL 엔트리 user::rwx (ACL_USER_OBJ) group::r-x (ACL_GROUP_OBJ) other::r-- (ACL_OTHER) mask::rwx (ACL_MASK) xattr: system.posix_acl_access 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_DATA0x0001r (4)
FILE_WRITE_DATA0x0002w (2)
FILE_EXECUTE0x0020x (1)
FILE_DELETE0x10000부모 디렉터리 w+x
FILE_READ_ATTRIBUTES0x0080stat() 가능
GENERIC_ALL0x10000000rwx (7)
GENERIC_READ0x80000000r (4)
GENERIC_WRITE0x40000000w (2)

세션 관리

ksmbd는 SMB2 세션 수립 과정(Negotiate → SessionSetup → TreeConnect)을 커널에서 직접 처리합니다. 세션 객체는 3계층 구조(Connection → Session → TreeConnect)로 관리됩니다.

SMB 클라이언트 ksmbd (서버) ① NEGOTIATE Request NEGOTIATE Response (Dialect=3.1.1) ② SESSION_SETUP Request (NTLM Negotiate) SESSION_SETUP Response (NTLM Challenge) SESSION_SETUP Request (NTLM Auth Response) Netlink → ksmbd.mountd 인증 SESSION_SETUP Response (SessionId=0x...) ③ TREE_CONNECT Request (\\server\share) TREE_CONNECT Response (TreeId=0x...) → 정상 파일 I/O (CREATE / READ / WRITE)

세션 객체 계층 구조

/* 연결 → 세션 → 트리 연결 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-H0x07모든 캐싱 활성화 (Exclusive 상태)
R-H0x03쓰기 break 후 읽기+핸들만 유지
R0x01쓰기+핸들 break 후 읽기만 유지
클라이언트 A ksmbd (oplock.c) 클라이언트 B 1. CREATE (file.txt, RWH lease 요청) CREATE Response (RWH lease 부여) RWH Lease (독점 캐시) 2. CREATE (file.txt) Lease Break 결정 3. OPLOCK_BREAK (RWH → R) 캐시 플러시 dirty 데이터 서버 전송 4. OPLOCK_BREAK Ack (R 수락) 5. CREATE Response (R lease 부여) R Lease (읽기만) R Lease (읽기만)
/* 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초 */
Lease Break 타임아웃: 클라이언트가 35초 내에 Lease Break에 응답하지 않으면, ksmbd는 강제로 oplock을 해제합니다. 네트워크 지연이 큰 환경에서는 이 타임아웃으로 인해 다른 클라이언트의 파일 접근이 지연될 수 있습니다. dmesg"oplock break timeout" 메시지가 반복된다면 네트워크 상태를 점검하세요.

암호화 및 서명

SMB3는 전송 중 데이터 보호를 위해 패킷 서명(signing)과 암호화(encryption)를 제공합니다. ksmbd는 커널 Crypto API를 활용하여 이를 구현합니다.

패킷 서명

SMB 버전서명 알고리즘키 파생
SMB2.0 / 2.1HMAC-SHA256SessionKey 직접 사용
SMB3.0 / 3.0.2AES-128-CMACKDF(SessionKey, "SMB2AESCMAC" + "SmbSign")
SMB3.1.1AES-128-CMAC 또는 AES-128-GMACKDF(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-SHA256SMB2.0~3.0hmac(sha256)
패킷 서명AES-128-CMACSMB3.0+cmac(aes)
암호화AES-128-CCMSMB3.0ccm(aes)
암호화AES-128-GCMSMB3.1.1gcm(aes)
암호화AES-256-CCMSMB3.1.1ccm(aes)
암호화AES-256-GCMSMB3.1.1gcm(aes)
Pre-auth 무결성SHA-512SMB3.1.1sha512
/* 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(Intel) 또는 ARMv8 Crypto Extension을 지원하는 CPU에서는 aes-ni 모듈이 자동으로 활성화되어 암호화 오버헤드가 최소화됩니다. cat /proc/crypto | grep -A4 "name.*gcm(aes)"로 하드웨어 가속 여부를 확인할 수 있습니다. 가속이 없으면 처리량이 30~50% 감소할 수 있습니다.

SMB3 멀티채널

SMB3 멀티채널은 하나의 SMB 세션을 여러 TCP 연결로 묶어 대역폭을 집계하고 네트워크 장애 시 투명한 페일오버를 제공합니다. ksmbd는 server multi channel support = yes 설정으로 이를 활성화합니다.

SMB 클라이언트 SMB 세션 (ID: 0xA1) NIC1: 10.0.0.10 (10G) NIC2: 10.0.1.10 (10G) NIC3: 10.0.2.10 (10G) NIC4: 192.168.0.10 (1G) TCP 연결 Channel 1 Channel 2 Channel 3 Channel 4 (느림) ksmbd 서버 ksmbd_session (ID: 0xA1) — 4개 채널 바인딩 ksmbd_conn #1 ksmbd_conn #2 ksmbd_conn #3 ksmbd_conn #4 공유 리소스 세션 키 (공유) 트리 연결 (공유) 파일 핸들 (공유) oplock 상태 (공유) 서명 키 (채널별 독립) 집계 대역폭: 10G + 10G + 10G + 1G = 최대 31 Gbps Channel 1 장애 시: Session은 유지, 나머지 3개 채널로 투명 페일오버
# 서버 설정 — /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);
    }
}
멀티채널과 서명: SMB3 멀티채널에서 각 채널은 독립적인 서명 키를 사용합니다. 세션 키는 공유하되, 서명 키는 채널별로 파생됩니다. 이는 한 채널이 탈취되어도 다른 채널의 서명을 위조할 수 없게 보장합니다.

Durable Handle과 Persistent Handle

Durable Handle은 네트워크 일시 단절 시 파일 핸들을 보존하여, 재연결 후 파일 상태를 복구할 수 있게 합니다. ksmbd는 Durable Handle v1/v2와 Persistent Handle을 모두 지원합니다.

핸들 유형도입 버전재연결 시간클러스터 지원설명
Durable Handle v1SMB2.1최대 60초아니오단일 서버 내 재연결
Durable Handle v2SMB3.0설정 가능아니오Create GUID로 핸들 식별
Persistent HandleSMB3.0무제한클러스터 페일오버 대응
Resilient HandleSMB2.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);
        }
    }
}
Durable Handle 사용 조건: Durable Handle은 Batch oplock 또는 RWH Lease를 보유한 파일에서만 유효합니다. 다른 클라이언트와 공유 중인 파일(Level II oplock)에서는 durable handle을 부여하지 않습니다. 이는 재연결 기간 동안 다른 클라이언트의 쓰기로 인한 데이터 불일치를 방지하기 위함입니다.

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 대체 */
성능 효과: Windows Explorer에서 디렉터리를 열 때 내부적으로 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 인프라를 활용합니다.

클라이언트 ksmbd VFS / fsnotify 1. CHANGE_NOTIFY (dir: /share/docs) 2. INTERIM Response (STATUS_PENDING) 3. inotify_add_watch() 대기 중... 파일 변경 발생! 4. fsnotify 이벤트 전달 5. CHANGE_NOTIFY Response (변경 목록) [Action: FILE_ACTION_ADDED, FileName: "report.docx"]

Change Notify 필터

필터비트감시 대상
FILE_NOTIFY_CHANGE_FILE_NAME0x001파일 생성, 삭제, 이름 변경
FILE_NOTIFY_CHANGE_DIR_NAME0x002디렉터리 생성, 삭제, 이름 변경
FILE_NOTIFY_CHANGE_ATTRIBUTES0x004파일 속성 변경
FILE_NOTIFY_CHANGE_SIZE0x008파일 크기 변경
FILE_NOTIFY_CHANGE_LAST_WRITE0x010최종 수정 시간 변경
FILE_NOTIFY_CHANGE_SECURITY0x100보안 디스크립터 변경
/* 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 호출로 변환하는 과정입니다. 읽기/쓰기 경로는 커널 페이지 캐시를 활용합니다.

READ 경로 smb2_read() — SMB READ 파싱 ksmbd_vfs_read() — VFS 래퍼 kernel_read() → vfs_read() 페이지 캐시 Hit 즉시 복사 페이지 캐시 Miss 블록 I/O → readahead 응답 버퍼에 데이터 복사 TCP/RDMA 전송 (선택: sendfile 최적화) WRITE 경로 smb2_write() — SMB WRITE 파싱 smb_break_all_write_oplock() — 쓰기 oplock break ksmbd_vfs_write() — VFS 래퍼 kernel_write() → vfs_write() 페이지 캐시에 기록 (writeback 예약) WriteThrough: fsync Lazy: writeback 대기 WRITE Response (실제 기록 바이트 수)
/* 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);
}
제로카피 최적화: ksmbd의 READ 경로는 splice 시스템 콜의 커널 내부 구현을 활용하여 페이지 캐시 → TCP 소켓 간 데이터를 사용자 공간 복사 없이 전송합니다. 이는 Samba가 커널↔사용자 공간 간 최소 2번의 복사를 수행하는 것과 비교하여 큰 파일 전송 시 CPU 사용량을 크게 줄입니다.

보안 기능

인증 방식

인증설명설정
NTLM (NTLMv2)NT 해시 기반 챌린지-응답기본 활성화
KerberosActive 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
CVE 주의: ksmbd는 커널 공간에서 실행되므로 취약점이 발견되면 커널 권한 상승으로 이어질 수 있습니다. 2022년 12월 CVE-2022-47939(CVSS 10.0)는 ksmbd의 use-after-free 취약점으로 원격 코드 실행이 가능했습니다. 반드시 최신 커널 패치를 적용하고, 불필요한 환경에서는 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 비교

항목Sambaksmbd
구현 위치사용자 공간 (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 / 임베디드
공존 가능: ksmbd와 Samba는 같은 시스템에서 동시에 실행할 수 없습니다 (TCP 445 포트 충돌). 하지만 ksmbd는 Samba의 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.cTCP 서버, 연결 수락, 워커 스레드ksmbd_server_init()
fs/ksmbd/connection.c연결 관리, 수신 루프ksmbd_conn_handler_loop()
fs/ksmbd/smb2pdu.cSMB2/3 PDU 처리 (명령 핸들러)smb2_negotiate(), smb2_open()
fs/ksmbd/smb2ops.cSMB2 연산자 테이블, 프로토콜 협상smb2_0_server_ops
fs/ksmbd/smb2misc.cSMB2 헤더 검증, 크레딧 관리ksmbd_smb2_check_message()
fs/ksmbd/auth.cNTLM/Kerberos 인증ksmbd_auth_ntlmv2()
fs/ksmbd/crypto_ctx.cAES-CCM/GCM 암호화, HMAC 서명ksmbd_sign_smb2_pdu()
fs/ksmbd/smbacl.cWindows DACL ↔ POSIX ACL 변환smb_get_acl(), uid_to_sid()
fs/ksmbd/mgmt/세션·트리·파일 핸들 관리user_session.c, tree_connect.c
fs/ksmbd/vfs.cVFS 래퍼 함수 (vfs_read/write 등)ksmbd_vfs_read(), ksmbd_vfs_write()
fs/ksmbd/vfs_cache.c파일 핸들 캐시ksmbd_lookup_fd_fast()
fs/ksmbd/oplock.coplock / SMB2 lease 구현opinfo_write_to_read()
fs/ksmbd/transport_tcp.cTCP 전송 계층tcp_transport_ops
fs/ksmbd/transport_rdma.cSMB Direct (RDMA) 전송smbd_transport_ops
fs/ksmbd/transport_ipc.cNetlink IPC 전송ksmbd_ipc_login_request()
fs/ksmbd/ndr.cNDR (Network Data Representation) 직렬화ndr_encode(), ndr_decode()
fs/ksmbd/unicode.cUTF-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
Wireshark 디코딩 팁: SMB3 암호화가 활성화되면 Wireshark에서 페이로드를 볼 수 없습니다. 디버깅 시 일시적으로 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 downksmbd.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 Chain (SHA-512) 초기값 64바이트 0x00 SHA-512 Hash || NegReq = PreauthHash₁ SHA-512 Hash₁ || NegRsp = PreauthHash₂ SHA-512 Hash₂ || SessReq = PreauthHash₃ 최종 해시 Hash₃ || SessRsp = FinalHash 세션 키 파생 (KDF) SigningKey = KDF-HMAC-SHA256(SessionBaseKey, "SMBSigningKey\0", FinalHash) EncryptionKey = KDF-HMAC-SHA256(SessionBaseKey, "SMBC2SCipherKey\0", FinalHash) MITM 방어 원리 중간자가 Negotiate 메시지를 변조하면 FinalHash가 불일치 → 세션 키 불일치 → 인증 실패 다운그레이드 공격 방어 공격자가 dialect를 SMB2.0으로 변조해도 Hash 불일치로 SESSION_SETUP 실패
/* 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_ENABLED0x01서명 가능클라이언트/서버 모두 설정 가능
SMB2_NEGOTIATE_SIGNING_REQUIRED0x02서명 필수server signing = required 시 설정
Dialect 다운그레이드 방어: SMB 3.1.1의 Pre-authentication Integrity Hash가 없는 SMB 3.0 이하에서는 Negotiate 메시지 변조를 통한 dialect 다운그레이드 공격이 가능합니다. 프로덕션 환경에서는 반드시 server min protocol = SMB3_11을 설정하여 SMB 3.1.1만 허용하는 것이 권장됩니다. ksmbd는 smb2ops.cksmbd_lookup_dialect()에서 서버 최소 버전을 먼저 확인합니다.

Capabilities 협상

Negotiate Response에 포함되는 Capabilities 플래그는 서버의 기능 지원 여부를 알려줍니다.

Capability비트설명ksmbd 지원
SMB2_GLOBAL_CAP_DFS0x01Distributed File System부분 지원 (DFS 루트)
SMB2_GLOBAL_CAP_LEASING0x02SMB2 Lease지원
SMB2_GLOBAL_CAP_LARGE_MTU0x04멀티크레딧 대용량 전송지원
SMB2_GLOBAL_CAP_MULTI_CHANNEL0x08멀티채널지원
SMB2_GLOBAL_CAP_PERSISTENT_HANDLES0x10Persistent Handle지원
SMB2_GLOBAL_CAP_DIRECTORY_LEASING0x20디렉터리 Lease지원
SMB2_GLOBAL_CAP_ENCRYPTION0x40SMB3 암호화지원
/* 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 인터페이스를 통해 구현합니다.

암호화 (Encrypt) — smb3_encrypt_resp() 평문 SMB2 PDU SMB2 Header(64B) + Payload Transform Header 생성 Nonce(12B GCM/11B CCM) SessionId, OrigSize AES-GCM Encrypt Key: EncryptionKey(16B) AAD: TransformHdr(20B) IV: Nonce(12B) 암호문 출력 EncryptedPayload + MAC Tag(16B) 전송되는 패킷 구조 TransformHdr(52B) Encrypted(SMB2 Header + Payload) MAC Tag(16B) 복호화 (Decrypt) — smb3_decrypt_req() 수신 패킷 TransformHdr + Ciphertext + MAC 세션/키 조회 SessionId → Session → DecryptionKey AES-GCM Decrypt Key: DecryptionKey(16B) MAC 검증 → 무결성 확인 실패 시 연결 해제 평문 복원 SMB2 Header + Payload AES-CCM (SMB 3.0) Nonce: 11바이트 | 직렬 처리 | 범용 CPU 최적 AES-GCM (SMB 3.1.1, 기본값) Nonce: 12바이트 | 병렬 처리 | AES-NI 하드웨어 가속

AES-CCM vs AES-GCM 비교

속성AES-128-CCMAES-128-GCMAES-256-GCM
SMB 버전SMB 3.0+SMB 3.1.1SMB 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 APIccm(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;
}
AES-NI 하드웨어 가속 확인: ksmbd의 암호화 성능은 CPU의 AES-NI 지원 여부에 크게 의존합니다. AES-NI가 없으면 AES-GCM 처리량이 5 GB/s에서 250 MB/s로 20배 감소합니다. 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 검증이 수행됩니다.

Channel 1 (기존 연결 - NIC1) NEGOTIATE → 완료 (Dialect 3.1.1) SESSION_SETUP → 인증 완료 (SessionId=0xA1) TREE_CONNECT → 공유 연결 완료 IOCTL(QUERY_NETWORK_INTERFACE_INFO) → 인터페이스 목록 수신 정상 파일 I/O 수행 중... Channel 2 (추가 연결 - NIC2) TCP 연결 수립 (다른 NIC/IP로) NEGOTIATE → 별도 PreauthHash 생성 SESSION_SETUP (Binding) SessionId=0xA1 (기존), Flags=BINDING 동일 사용자 인증 수행 바인딩 검증 1. 동일 사용자 확인 2. PreauthHash 기반 서명 키 파생 SESSION_SETUP Response → 바인딩 완료 동일 세션으로 파일 I/O 병렬 처리

I/O 분배 정책

분배 정책설명적합한 환경
Round-Robin채널을 순환하며 요청 분배동일 속도 NIC 환경
Weighted Round-Robin링크 속도에 비례하여 분배이종 NIC 혼합 (10G + 1G)
Least Outstanding미완료 요청이 가장 적은 채널에 분배지연 변동이 큰 환경
Hash-based파일 ID 기반 해시로 채널 고정순차 I/O 최적화 (순서 보장)
채널 장애 감지: ksmbd는 각 채널의 TCP 연결 상태를 독립적으로 모니터링합니다. 한 채널의 TCP 연결이 끊어지면 ksmbd_conn_alive()가 false를 반환하고, 해당 채널만 정리됩니다. 세션은 다른 채널이 하나라도 살아있으면 유지됩니다. 클라이언트는 끊어진 채널을 통해 전송 중이던 요청을 다른 채널로 자동 재전송합니다.

Oplock/Lease 상태 머신

ksmbd의 oplock/lease 관리는 복잡한 상태 전이를 포함합니다. 파일이 열릴 때 oplock이 부여되고, 충돌하는 접근이 발생하면 단계적으로 oplock이 축소(break)됩니다. 이 상태 전이를 이해하는 것이 ksmbd 디버깅의 핵심입니다.

RWH (Exclusive) 읽기+쓰기+핸들 캐시 RH 읽기+핸들 캐시 RW 읽기+쓰기 캐시 R (Shared) 읽기 캐시만 None 캐시 없음 Write Break (다른 클라이언트 쓰기) Handle Break (다른 클라이언트 삭제) Write+Handle Break Handle Break Write Break Read Break Full Break (타임아웃) Lease State 전이 트리거 Write Break: 다른 클라이언트의 쓰기/잠금 요청 Handle Break: 파일 삭제/이름 변경 요청

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 Break35초디렉터리 캐시 무효화
네트워크 단절TCP keepalive연결 정리 후 모든 oplock 해제
Oplock Break 경합: 다수의 클라이언트가 동시에 같은 파일에 접근하면 oplock break가 연쇄적으로 발생하여 성능이 급격히 저하될 수 있습니다(oplock thrashing). 이 현상은 다수 사용자가 동시에 편집하는 Office 문서에서 자주 발생합니다. 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 수명 주기

1. 핸들 생성 CREATE + DurableV2 CreateGuid 발급 RWH Lease 부여 2. 정상 사용 READ/WRITE I/O oplock 유지 fp->is_durable=true 3. 연결 단절 TCP 연결 끊김 타이머 시작 핸들 고아(orphan) 상태 4a. 재연결 성공 CREATE + Reconnect CreateGuid 매칭 핸들 복원 완료 4b. 타임아웃 60초(v1) / 설정값(v2) 핸들 영구 삭제 Durable Handle 유형별 재연결 동작 Durable v1 SMB 2.1+ 타임아웃: 60초 고정 식별: FileId Batch oplock 필수 Durable v2 SMB 3.0+ 타임아웃: 설정 가능 식별: CreateGuid(16B) RWH Lease 필수 Persistent SMB 3.0+ 타임아웃: 무제한 클러스터 페일오버 CA 공유 필수 Resilient SMB 2.1+ 타임아웃: 최대 300초 IOCTL로 설정 oplock 불요
/* 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;
}
Durable Handle과 데이터 무결성: Durable Handle이 고아 상태(연결 끊김)인 동안 파일 데이터는 서버 페이지 캐시에 남아있지만, 다른 클라이언트는 해당 파일의 쓰기가 차단됩니다(oplock이 유지되기 때문). 재연결 시 클라이언트는 로컬 캐시의 dirty 데이터를 서버에 플러시합니다. 이 메커니즘 덕분에 짧은 네트워크 단절에도 데이터 손실 없이 작업을 계속할 수 있습니다.

워커 스레드풀 아키텍처 심화

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;
}
워크큐 모니터링: ksmbd 워크큐의 상태를 확인하려면 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)

단일 TCP 세그먼트 (하나의 SMB 메시지) 명령 1: CREATE SMB2 Header (64B) NextCommand = 오프셋 → 명령2 Flags = 0x00 (첫 번째) FileName: "report.docx" → 반환: FileId=0x1234 명령 2: READ SMB2 Header (64B) NextCommand = 오프셋 → 명령3 Flags = RELATED_OPERATIONS FileId = 0xFFFFFFFF (이전 결과) → 실제: FileId=0x1234 대체 명령 3: CLOSE SMB2 Header (64B) NextCommand = 0 (마지막) Flags = RELATED_OPERATIONS FileId = 0xFFFFFFFF (이전 결과) → 실제: FileId=0x1234 대체 Compound 이점 RTT 3회 → 1회 (66% 절감) TCP 세그먼트 6개 → 2개 원자적 처리 (중간 상태 노출 방지) 에러 전파 규칙 Related: 앞 명령 실패 시 뒤 모두 실패 Unrelated: 개별 명령 독립 실패 각 명령의 Status 필드에 결과 기록

Windows 클라이언트의 Compound 활용 패턴

사용 패턴Compound 구성용도
파일 읽기CREATE + READ + CLOSEWindows Explorer에서 파일 미리보기
파일 속성 조회CREATE + QUERY_INFO + CLOSE파일 속성 대화 상자
파일 쓰기CREATE + WRITE + FLUSH + CLOSEOffice 문서 저장
디렉터리 목록CREATE + QUERY_DIRECTORY + CLOSE폴더 탐색
파일 이동CREATE + SET_INFO(rename) + CLOSE파일 이름 변경
보안 조회CREATE + QUERY_INFO(security) + CLOSE파일 권한 확인
Compound 디버깅: Wireshark에서 compound request를 분석할 때, 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 응답 형식

필드크기설명
NextEntryOffset4바이트다음 변경 항목까지 오프셋 (0이면 마지막)
Action4바이트FILE_ACTION_ADDED/REMOVED/MODIFIED/...
FileNameLength4바이트파일 이름 바이트 수 (UTF-16LE)
FileName가변변경된 파일/디렉터리 이름 (UTF-16LE)
Change Notify 리소스 사용: 대규모 디렉터리(수만 개 파일)에 대한 재귀 감시(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 토큰 처리

SPNEGO (Simple and Protected GSSAPI Negotiation) SESSION_SETUP 요청의 SecurityBuffer에 SPNEGO 토큰 포함 NegTokenInit (클라이언트 → 서버) mechTypes: [Kerberos(1.2.840.113554.1.2.2), NTLM(1.3.6.1.4.1.311.2.2.10)] mechToken: NTLMSSP_NEGOTIATE 또는 AP-REQ ASN.1 DER 인코딩 Kerberos 우선 → 실패 시 NTLM 폴백 NegTokenTarg (서버 → 클라이언트) negResult: accept-incomplete / accept-completed supportedMech: 선택된 메커니즘 responseToken: NTLMSSP_CHALLENGE 또는 AP-REP mechListMIC: 무결성 검증 (선택) ksmbd SPNEGO 처리 (auth.c) NTLM 경로 커널: NTLMSSP 파싱, 챌린지 생성, NTProofStr 검증 ksmbd.mountd: NT Hash 조회 (Netlink) Kerberos 경로 커널: SPNEGO/AP-REQ 추출 ksmbd.mountd: keytab으로 티켓 검증 (Netlink RPC)

NTLMv2 세션 보안 레벨

보안 레벨설명해시 알고리즘ksmbd 지원
LM Response레거시 LAN Manager 해시 (매우 취약)DES(password → 14자 패딩)미지원
NTLM ResponseNT Hash 기반 (NTLMv1)MD4(password) + DES미지원
NTLMv2 ResponseHMACv2 기반 (권장)HMAC-MD5(NTOWFv2, Challenge + Blob)지원 (기본)
NTLMv2 + Session SecurityNTLMv2 + 세션 서명/실링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;
}
NTLM Relay 공격 방어: NTLMv2 인증은 챌린지-응답 방식이므로 릴레이 공격에 취약할 수 있습니다. ksmbd에서는 server signing = required를 설정하여 모든 SMB 패킷에 서명을 강제하면 릴레이 공격을 방지할 수 있습니다. SMB 3.1.1의 Pre-authentication Integrity Hash를 사용하면 더 강력한 보호가 가능합니다. 가능하면 Kerberos 인증을 사용하는 것이 가장 안전합니다.

ACL 매핑 (POSIX ↔ Windows) 심화

Windows와 POSIX의 접근 제어 모델은 근본적으로 다르며, ksmbd는 smbacl.c에서 이 두 모델 간의 최선의 변환을 수행합니다. 변환 과정에서 의미가 완전히 보존되지 않는 경우가 있어 주의가 필요합니다.

변환 규칙 상세

Windows ACEPOSIX 매핑역방향 손실
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 ACEdefault ACL일부 상속 플래그 손실
GENERIC_ALL + DELETE + WRITE_DACrwx (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접근 ACLPOSIX ACL 바이너리
system.posix_acl_default기본(상속) ACLPOSIX ACL 바이너리
security.NTACLWindows NT Security Descriptor 캐시NDR 인코딩된 NTSD
user.DOSATTRIBDOS 파일 속성 (읽기전용, 숨김 등)텍스트 ("0x0020")
user.DOSSTREAM.*Alternate Data Stream스트림 데이터
ACL 상속: Windows에서는 디렉터리의 ACL이 새로 생성된 파일에 자동 상속됩니다. ksmbd는 이를 POSIX default ACL로 매핑합니다. inherit acls = yes를 smb.conf에 설정하면 부모 디렉터리의 POSIX default ACL이 자식 파일에 적용됩니다. 그러나 POSIX default ACL은 Windows의 세밀한 상속 규칙(OBJECT_INHERIT, CONTAINER_INHERIT, INHERIT_ONLY)을 완전히 재현하지 못합니다.

성능 튜닝 심화

ksmbd의 성능 최적화는 커널 파라미터, 파일시스템 옵션, 네트워크 설정, SMB 프로토콜 옵션의 조합으로 이루어집니다. 워크로드 특성에 따라 적절한 튜닝 전략이 달라집니다.

병목 지점 분석

SMB 요청 처리 경로 (READ 기준) 네트워크 수신 TCP 수신 버퍼 NIC 인터럽트 복사: 1회 복호화 AES-GCM CPU 집약적 AES-NI 의존 명령 파싱 헤더 검증 세션 조회 빠름 (O(1)) VFS I/O 페이지 캐시 hit/miss 블록 I/O 대기 가장 큰 변동 네트워크 전송 TCP 전송 버퍼 splice 제로카피 NIC 대역폭 한계 워크로드별 주요 병목 지점 순차 대용량 I/O 병목: 네트워크 대역폭 해결: 멀티채널, RDMA, Jumbo Frame 랜덤 소규모 I/O 병목: 스토리지 IOPS + RTT 해결: NVMe, Compound, 캐시 메타데이터 (stat/open) 병목: oplock Break + 잠금 경합 해결: Lease, 캐시 정책 조정 암호화 활성화 시 추가 병목 AES-NI 없으면 CPU 30~50% 추가 소비 | AES-NI 있으면 5~10% 수준

성능 튜닝 체크리스트

계층파라미터기본값권장값효과
SMBsmb2 max read/write1MB8MB순차 I/O 처리량 향상
SMBsmb2 leasesyesyes클라이언트 캐싱 활성화
SMBserver multi channel supportnoyes (다수 NIC)대역폭 집계
SMBdurable handleyesyes재연결 시 핸들 보존
TCPnet.core.rmem_max21299216777216대용량 수신 버퍼
TCPnet.core.wmem_max21299216777216대용량 전송 버퍼
TCPnet.ipv4.tcp_rmem4096 131072 62914564096 87380 16777216TCP 자동 튜닝 범위 확대
TCPnet.core.netdev_max_backlog10005000고속 NIC 패킷 큐
FSmount option: noatime미적용noatime읽기 시 atime 갱신 불필요
FSmount option: data=writebackdata=orderedwriteback (ext4)쓰기 성능 향상 (안전성 절충)
시스템vm.dirty_ratio2040쓰기 캐시 비율 증가
시스템vm.dirty_background_ratio105조기 비동기 플러시

성능 모니터링 명령

# 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
RDMA(SMB Direct) 성능: RDMA를 지원하는 NIC(Mellanox ConnectX, Intel E810 등)가 있다면 CONFIG_SMB_SERVER_SMBDIRECT=y로 빌드하여 SMB Direct를 활성화하세요. RDMA는 CPU 개입 없이 NIC 간 직접 메모리 전송을 수행하므로, 100 Gbps 이상의 환경에서 TCP 대비 40~60% 높은 처리량과 50% 낮은 CPU 사용률을 달성합니다. 클라이언트에서는 rdma 마운트 옵션을 사용합니다.

워크로드별 최적 설정 프로파일

워크로드smb2 max read/writecache 옵션멀티채널암호화추가 권장 사항
대용량 영상 편집8MBlooseyes (4채널)offJumbo Frame 9000, RDMA 권장
Office 문서 협업1MBstrictyesonLease 활성화, oplock thrashing 주의
데이터베이스 백업8MBnoneyesonWriteThrough, data=journal
소프트웨어 빌드4MBstrictyesoffnoatime, Compound 활용
웹 서버 정적 콘텐츠4MBloosenooffread only, 페이지 캐시 극대화
가상 머신 스토리지8MBnoneyes (2채널)offDirect 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
NUMA 비대칭 주의: 멀티채널 환경에서 각 NIC가 서로 다른 NUMA 노드에 있고, 공유 디렉터리의 스토리지가 특정 NUMA 노드에만 연결된 경우 원격 메모리 접근이 빈번해져 성능이 저하될 수 있습니다. 이 경우 irqbalance 대신 수동으로 IRQ 친화도를 설정하고, 스토리지와 같은 NUMA 노드의 NIC를 우선 사용하도록 멀티채널 설정을 조정하세요. lstopo(hwloc 패키지) 명령으로 NUMA 토폴로지를 시각적으로 확인할 수 있습니다.

ksmbd와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.