Kernel TLS (kTLS)

Linux Kernel TLS(kTLS)를 심층 분석합니다. TLS 1.2/1.3 레코드 처리의 커널 오프로딩 경로, ULP 기반 소켓 통합, sendfile/zero-copy 성능 가속, NIC 암호화 오프로드, OpenSSL·Nginx 연계 방식, 적용 가능한 트래픽 패턴과 한계, 성능 회귀·호환성 이슈를 진단하는 운영 디버깅 절차까지 실무 관점으로 설명합니다.

전제 조건: 네트워크 스택커널 보안 문서를 먼저 읽으세요. 보안 네트워킹은 정책 결정 경로와 실제 암복호화/필터 경로를 함께 보아야 운영 사고를 줄일 수 있습니다.
일상 비유: 이 주제는 출입 통제와 봉인 검사와 비슷합니다. 패킷을 통과시키기 전에 규칙 검사와 신원 확인을 거치듯이, 정책과 데이터 경로의 결합이 핵심입니다.

핵심 요약

  • 패킷 수명주기 — ingress, 처리, egress 경로를 연결합니다.
  • 큐/버퍼 모델 — sk_buff와 큐 지점의 역할을 분리합니다.
  • 정책/데이터 분리 — 제어 평면과 데이터 평면을 구분합니다.
  • 성능 지표 — PPS, 지연, 드롭 원인을 함께 분석합니다.
  • 오프로딩 경계 — NIC/XDP/DPDK 경계를 명확히 유지합니다.

단계별 이해

  1. 경로 고정
    문제가 발생한 ingress/egress 지점을 먼저 특정합니다.
  2. 큐 관찰
    백로그와 드롭 위치를 계측합니다.
  3. 정책 반영 확인
    라우팅/필터 변경이 데이터 경로에 반영됐는지 봅니다.
  4. 부하 검증
    실제 트래픽 패턴에서 재현성을 확인합니다.
관련 표준: RFC 8446 (TLS 1.3), RFC 5246 (TLS 1.2) — TLS 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

kTLS (Kernel TLS) 심화

kTLS (Kernel TLS)는 TLS 레코드 계층의 암호화/복호화를 커널 소켓 계층에서 수행하는 메커니즘입니다. 전통적으로 TLS는 OpenSSL, GnuTLS 등 유저스페이스 라이브러리가 전담했지만, 커널 4.13(TX)과 4.17(RX)부터 대칭키 암호화 처리를 커널로 이동시켜 sendfile() 제로카피, splice, NIC 하드웨어 오프로드 등의 이점을 제공합니다.

kTLS 핵심 포인트: kTLS는 TLS 핸드셰이크를 커널에서 수행하지 않습니다. 핸드셰이크(인증서 검증, 키 교환 등)는 여전히 유저스페이스 TLS 라이브러리가 처리하며, 핸드셰이크 완료 후 협상된 대칭키(session key)를 setsockopt(SOL_TLS)로 커널에 전달합니다. 이후의 레코드 암호화/복호화만 커널이 처리합니다.

kTLS 아키텍처

User Space Application (nginx, HAProxy, ...) TLS Library (OpenSSL, GnuTLS) sendfile() 제로카피 전송 splice() 파이프 전달 Kernel Space kTLS (net/tls/) TLS 레코드 암호화/복호화 · sendfile 제로카피 Crypto Framework (Crypto API) AES-GCM, ChaCha20-Poly1305 TCP 전송/수신 큐 NIC TLS Offload (선택적) — tls-hw-tx/rx-offload NIC Hardware SW kTLS: 커널 CPU 암호화 HW kTLS TX: NIC가 암호화 HW kTLS RX: NIC가 복호화 (대부분의 NIC) (ConnectX-6+, E810, CX7) (ConnectX-6 Dx+, CX7) setsockopt(SOL_TLS)

유저스페이스 TLS vs kTLS 비교

항목유저스페이스 TLS (전통적)kTLS (커널 TLS)
암호화 위치 유저스페이스 (OpenSSL 등) 커널 소켓 계층 (net/tls/)
핸드셰이크 유저스페이스 유저스페이스 (동일)
sendfile() 지원 불가 — 데이터를 유저스페이스로 읽고 암호화 후 send() 가능 — 커널이 파일 → 암호화 → TCP 직접 전달 (제로카피)
splice() 지원 불가 가능 — 파이프를 통한 커널 내 데이터 전달
컨텍스트 스위칭 read() → 유저스페이스 암호화 → write() (2회 syscall) sendfile() 1회 syscall로 완료
메모리 복사 커널→유저→(암호화)→커널 (2~3회 복사) 커널 내 처리 (0~1회 복사)
HW 오프로드 불가 (NIC가 유저스페이스 버퍼에 접근 불가) 가능 — NIC가 TLS 레코드 암/복호화 수행
정적 파일 서빙 성능 기준 (1x) sendfile kTLS: ~2-4x 처리량 향상
커널 버전 요구 제한 없음 TX: 4.13+, RX: 4.17+, TLS 1.3: 5.1+

kTLS 커널 내부 구조체

/* include/net/tls.h — kTLS 핵심 구조체 */

/* TLS 버전 및 암호 스위트 정보를 담는 컨텍스트 */
struct tls_context {
    struct tls_prot_info  prot_info;     /* TLS 버전, 암호 알고리즘 정보 */

    u8 tx_conf : 3;                      /* TX 모드: SW, HW, HW_RECORD */
    u8 rx_conf : 3;                      /* RX 모드: SW, HW, HW_RECORD */
    u8 zerocopy_sendfile : 1;            /* 제로카피 sendfile 지원 여부 */
    u8 rx_no_pad : 1;                    /* TLS 1.3 패딩 비활성화 */

    int (*push_pending_record)(struct sock *sk, int flags);
    void (*sk_write_space)(struct sock *sk);

    void *priv_ctx_tx;                  /* TX 구현별 컨텍스트 (SW 또는 HW) */
    void *priv_ctx_rx;                  /* RX 구현별 컨텍스트 */

    struct net_device *netdev;           /* HW offload 디바이스 (NULL = SW) */

    struct cipher_context tx;            /* 송신 암호 컨텍스트 */
    struct cipher_context rx;            /* 수신 암호 컨텍스트 */

    struct scatterlist *partially_sent_record;
    u16 partially_sent_offset;

    struct list_head list;              /* 전역 컨텍스트 리스트 */
    refcount_t refcount;
    struct rcu_head rcu;
};

/* TLS 프로토콜 정보 */
struct tls_prot_info {
    u16 version;                        /* TLS_1_2_VERSION / TLS_1_3_VERSION */
    u16 cipher_type;                    /* TLS_CIPHER_AES_GCM_128 등 */
    u16 prepend_size;                   /* 레코드 헤더 크기 */
    u16 tag_size;                       /* AEAD 태그 크기 (16바이트 for GCM) */
    u16 overhead_size;                  /* 전체 오버헤드 = prepend + tag */
    u16 iv_size;                        /* IV 크기 */
    u16 salt_size;                      /* salt 크기 (GCM: 4바이트) */
    u16 rec_seq_size;                   /* 레코드 시퀀스 번호 크기 */
    u16 aad_size;                       /* AAD (Additional Auth Data) 크기 */
    u16 tail_size;                      /* TLS 1.3 content type (1바이트) */
};

/* 소프트웨어 TX 컨텍스트 */
struct tls_sw_context_tx {
    struct crypto_aead *aead_send;      /* AEAD 암호 인스턴스 (AES-GCM 등) */
    struct tls_strparser strp;          /* TLS 레코드 파서 */
    struct sk_msg tx_msg;
    struct list_head tx_list;           /* 전송 대기 레코드 리스트 */
    atomic_t encrypt_pending;           /* 비동기 암호화 진행 카운트 */
    spinlock_t encrypt_compl_lock;
    int async_notify;
    u8 async_capable : 1;               /* 비동기 암호화 가능 여부 */

    /* 비동기 암호화: 암호화 완료를 기다리지 않고
     * 다음 레코드를 처리하여 파이프라인 효율 향상.
     * 완료 콜백에서 TCP 전송 큐에 데이터 삽입. */
};

/* 소프트웨어 RX 컨텍스트 */
struct tls_sw_context_rx {
    struct crypto_aead *aead_recv;      /* AEAD 복호 인스턴스 */
    struct strparser strp;              /* TCP 바이트스트림 → TLS 레코드 분리 */
    struct sk_buff_head rx_list;        /* 복호화된 레코드 리스트 */
    void (*saved_data_ready)(struct sock *sk);
    struct sk_buff *recv_pkt;          /* 현재 처리 중인 수신 레코드 */
    u8 reader_present;
    u8 async_capable : 1;
    u8 zc_capable : 1;                 /* 수신 제로카피 가능 */
    u8 reader_contended : 1;
    atomic_t decrypt_pending;
};
strparser의 역할: TCP는 바이트 스트림이므로 TLS 레코드 경계가 TCP 세그먼트와 일치하지 않을 수 있습니다. strparser(net/strparser/)는 TCP 수신 데이터를 파싱하여 완전한 TLS 레코드를 추출합니다. TLS 레코드 헤더의 길이 필드를 읽어 레코드가 완성될 때까지 데이터를 축적하고, 완전한 레코드가 모이면 복호화 콜백을 호출합니다.

kTLS 설정 API — setsockopt() 흐름

kTLS 활성화는 TLS 핸드셰이크 완료 후 setsockopt()로 대칭키를 커널에 전달하는 것으로 시작됩니다. OpenSSL 3.0+에서는 SSL_set_options(ssl, SSL_OP_ENABLE_KTLS)로 자동 처리됩니다.

/* include/uapi/linux/tls.h — 유저스페이스 API 정의 */

/* TLS 소켓 옵션 레벨 */
#define SOL_TLS     282

/* TLS 소켓 옵션 */
#define TLS_TX      1     /* 송신(TX) kTLS 활성화 */
#define TLS_RX      2     /* 수신(RX) kTLS 활성화 */

/* 지원 TLS 버전 */
#define TLS_1_2_VERSION    0x0303
#define TLS_1_3_VERSION    0x0304

/* 지원 암호 스위트 */
#define TLS_CIPHER_AES_GCM_128            51
#define TLS_CIPHER_AES_GCM_256            52
#define TLS_CIPHER_AES_CCM_128            53
#define TLS_CIPHER_CHACHA20_POLY1305      54
#define TLS_CIPHER_SM4_GCM                55  /* 커널 6.0+ */
#define TLS_CIPHER_SM4_CCM                56  /* 커널 6.0+ */
#define TLS_CIPHER_ARIA_GCM_128           57  /* 커널 6.2+ */
#define TLS_CIPHER_ARIA_GCM_256           58  /* 커널 6.2+ */

/* AES-128-GCM 암호 정보 구조체 (가장 널리 사용) */
struct tls12_crypto_info_aes_gcm_128 {
    struct tls_crypto_info info;       /* version + cipher_type */
    unsigned char iv[8];              /* 명시적 IV (nonce의 가변 부분) */
    unsigned char key[16];            /* AES-128 대칭키 */
    unsigned char salt[4];            /* 암묵적 nonce (고정 부분) */
    unsigned char rec_seq[8];         /* 초기 레코드 시퀀스 번호 */
};

/* AES-256-GCM 암호 정보 구조체 */
struct tls12_crypto_info_aes_gcm_256 {
    struct tls_crypto_info info;
    unsigned char iv[8];
    unsigned char key[32];            /* AES-256 대칭키 */
    unsigned char salt[4];
    unsigned char rec_seq[8];
};

/* ChaCha20-Poly1305 암호 정보 구조체 */
struct tls12_crypto_info_chacha20_poly1305 {
    struct tls_crypto_info info;
    unsigned char iv[12];
    unsigned char key[32];
    unsigned char salt[0];            /* ChaCha20에는 salt 없음 */
    unsigned char rec_seq[8];
};
/* ━━━ kTLS 활성화 예제 (유저스페이스 C 코드) ━━━ */

#include <linux/tls.h>
#include <netinet/tcp.h>

/* 1. 일반 TCP 소켓 생성 + TLS 핸드셰이크 (OpenSSL 등) */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* ... connect() + SSL_do_handshake() ... */

/* 2. 핸드셰이크 후 대칭키 추출 (TLS 1.2 + AES-128-GCM 예시) */
struct tls12_crypto_info_aes_gcm_128 crypto_info;
memset(&crypto_info, 0, sizeof(crypto_info));

crypto_info.info.version = TLS_1_2_VERSION;
crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_128;

/* OpenSSL에서 키/IV/시퀀스 번호 추출 */
SSL_get_key_material(ssl, crypto_info.key, crypto_info.iv,
                     crypto_info.salt, crypto_info.rec_seq);

/* 3. SOL_TLS + TLS_TX: 커널에 TX 대칭키 전달 → kTLS TX 활성화 */
setsockopt(sockfd, SOL_TLS, TLS_TX,
           &crypto_info, sizeof(crypto_info));

/* 4. SOL_TLS + TLS_RX: 커널에 RX 대칭키 전달 → kTLS RX 활성화 */
struct tls12_crypto_info_aes_gcm_128 crypto_info_rx;
/* ... RX 키 설정 ... */
setsockopt(sockfd, SOL_TLS, TLS_RX,
           &crypto_info_rx, sizeof(crypto_info_rx));

/* 5. 이후 send()/recv()는 커널이 자동으로 TLS 레코드 암/복호화
 *    sendfile()도 제로카피로 동작 */
sendfile(sockfd, filefd, &offset, count);
/* → 커널: 파일 페이지 → AES-GCM 암호화 → TLS 레코드 → TCP 전송
 *   유저스페이스 복사 없음! */

kTLS TX 경로 — 송신 처리

/* net/tls/tls_main.c — kTLS 초기화 */

/* setsockopt(SOL_TLS, TLS_TX) 호출 시 진입 */
static int do_tls_setsockopt_conf(struct sock *sk,
                                  sockptr_t optval, unsigned int optlen,
                                  int tx)
{
    struct tls_context *ctx = tls_get_ctx(sk);
    struct tls_crypto_info *crypto_info;

    /* 1. 유저스페이스에서 전달한 암호 정보 복사 */
    copy_from_sockptr(&crypto_info, optval, ...);

    /* 2. NIC HW offload 시도 */
    if (tx)
        rc = tls_set_device_offload(sk, ctx);  /* HW 가능 시 HW 모드 */

    /* 3. HW 불가 → SW 모드 폴백 */
    if (rc)
        rc = tls_set_sw_offload(sk, ctx, tx);
    /* → AEAD 인스턴스(aes-gcm) 할당
     * → TCP prot을 tls_prots[TLS_SW]로 교체
     * → sendmsg/sendpage 콜백이 kTLS 함수로 후킹 */
}

/* net/tls/tls_sw.c — 소프트웨어 TX 경로 */

/* send() / sendmsg() 시 호출되는 kTLS TX 함수 */
int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
    struct tls_context *tls_ctx = tls_get_ctx(sk);
    struct tls_sw_context_tx *ctx = tls_ctx->priv_ctx_tx;

    /* 루프: 유저 데이터를 TLS 레코드 크기(max 16KB)로 분할 */
    while (msg_data_left(msg)) {
        /* a) 유저 데이터를 scatterlist에 수집 */
        tls_push_data(sk, msg, size, flags, TLS_RECORD_TYPE_DATA);

        /* b) TLS 레코드 헤더 생성 (content type, version, length) */
        tls_fill_prepend(tls_ctx, rec->aad_space, ...);

        /* c) AEAD 암호화 (AES-128-GCM) */
        crypto_aead_encrypt(aead_req);
        /* → IV(nonce) = salt(4B) || explicit_iv(8B)
         * → AAD = seq_num(8B) || record_header(5B)  [TLS 1.2]
         * → AAD = record_header(5B)                   [TLS 1.3]
         * → 평문 → AES-GCM → 암호문 + 16B 태그 */

        /* d) 암호화된 레코드를 TCP 전송 큐에 삽입 */
        tls_push_record(sk, flags, record_type);
    }
}

/* sendfile() 제로카피 경로 */
int tls_sw_sendpage(struct sock *sk, struct page *page,
                    int offset, size_t size, int flags)
{
    /* sendfile() → do_sendpage() → tls_sw_sendpage()
     *
     * 파일 페이지 캐시를 직접 scatterlist에 매핑:
     *   page cache → sg_set_page(sg, page, ...) → AEAD 암호화 → TCP
     *
     * 유저스페이스로의 데이터 복사 없음!
     * 기존 방식: read(file→user) + SSL_write(user→kernel) = 2회 복사
     * kTLS sendfile: 파일 → 커널 암호화 → TCP = 0회 유저 복사 */
}

kTLS RX 경로 — 수신 처리

/* net/tls/tls_sw.c — 소프트웨어 RX 경로 */

/* strparser가 완전한 TLS 레코드를 수신하면 호출 */
static void tls_strp_msg_ready(struct tls_strparser *strp,
                               struct sk_buff *skb)
{
    /* TCP 바이트 스트림에서 완전한 TLS 레코드를 감지
     *
     * TLS Record 구조:
     * ┌──────────┬─────────┬────────┬───────────┬─────┐
     * │ContentType│ Version │ Length │ Encrypted │ Tag │
     * │  (1B)    │  (2B)   │ (2B)   │  Payload  │(16B)│
     * └──────────┴─────────┴────────┴───────────┴─────┘
     * ← 5B header →        ← Length bytes →
     *
     * strparser가 Length 필드를 파싱하여 완전한 레코드 경계 결정
     */
}

/* recv() / recvmsg() 시 호출 */
int tls_sw_recvmsg(struct sock *sk, struct msghdr *msg,
                   size_t len, int flags, int *addr_len)
{
    struct tls_sw_context_rx *ctx = ...;

    /* 1. strparser가 축적한 완전한 TLS 레코드 가져오기 */
    skb = tls_strp_msg_dequeue(&ctx->strp);

    /* 2. TLS 레코드 헤더에서 content type 확인 */
    /* - TLS_RECORD_TYPE_DATA(23): 일반 데이터 → 복호화 후 유저에 전달
     * - TLS_RECORD_TYPE_ALERT(21): TLS 경고 → 에러 처리
     * - TLS_RECORD_TYPE_HANDSHAKE(22): 재협상 → 유저스페이스에 전달
     * - TLS 1.3: 항목이 항상 APPLICATION_DATA(23)이고,
     *   실제 content type은 복호화 후 마지막 바이트에서 확인 */

    /* 3. AEAD 복호화 */
    err = decrypt_skb(sk, skb, msg);
    /* → crypto_aead_decrypt(aead_req)
     * → MAC(GCM 태그) 검증 + 복호화
     * → 실패 시 TLS_ALERT_BAD_RECORD_MAC 반환 */

    /* 4. 복호화된 평문을 유저스페이스 버퍼에 복사 */
    err = skb_copy_datagram_msg(skb, rxm->offset, msg, chunk);

    /* 제로카피 RX (TLS_RX_EXPECT_NO_PAD 설정 시):
     * 유저 버퍼에 직접 복호화하여 중간 커널 버퍼 복사 제거 */
}

sendfile() 제로카피 — kTLS의 핵심 이점

kTLS의 가장 큰 이점은 sendfile() 시스템 콜을 TLS 연결에서 사용할 수 있다는 것입니다. 웹서버의 정적 파일 서빙에서 유저스페이스 데이터 복사를 완전히 제거하여 처리량을 크게 향상시킵니다.

기존 방식 (유저스페이스 TLS) Page Cache ①복사 User Buffer ②암호화 SSL_write() ③복사 TCP → read() + OpenSSL 암호화 + send() = 2회 syscall, 2~3회 메모리 복사 kTLS SW sendfile() Page Cache 직접 참조 kTLS 암호화 TCP → sendfile() 1회 syscall, 유저스페이스 복사 0회 (페이지 캐시 직접 암호화) kTLS HW Offload sendfile() Page Cache DMA TCP NIC 암호화 (HW AES-GCM) → sendfile() 1회 syscall, CPU 암호화 0% (NIC가 전담)
/* ━━━ nginx kTLS sendfile 예시 ━━━ */

/* nginx 1.21.4+ 에서 kTLS 활성화 시:
 *
 * 정적 파일 서빙 흐름:
 * 1. 클라이언트 TLS 핸드셰이크 (OpenSSL)
 * 2. OpenSSL이 setsockopt(SOL_TLS, TLS_TX) 호출 → kTLS TX 활성화
 * 3. 파일 요청 시 sendfile(client_fd, file_fd, ...) 직접 호출
 * 4. 커널: 페이지 캐시 → kTLS 암호화 → TCP 전송
 *
 * nginx.conf 설정:
 * http {
 *     ssl_conf_command Options KTLS;  ← kTLS 활성화
 *     sendfile on;                     ← sendfile 활성화
 * }
 */

/* 커널 sendfile 경로 (kTLS 활성화 시):
 *
 * sys_sendfile64()
 *   → do_sendfile()
 *     → do_splice_direct()
 *       → splice_file_to_pipe()     ← 파일 → pipe (페이지 참조)
 *       → pipe_to_sendpage()
 *         → tls_sw_sendpage()       ← kTLS: 페이지 → 암호화 → TCP
 *           → tls_push_data()
 *           → tls_do_encryption()   ← AES-GCM 암호화
 *           → tls_push_record()     ← TCP 전송 큐 삽입
 */

/* TLS_TX_ZEROCOPY_RO: 읽기 전용 페이지의 진정한 제로카피 (커널 5.19+)
 *
 * 일반 sendfile: 페이지 복사 후 암호화 (COW 방지)
 * ZEROCOPY_RO:   페이지 직접 암호화 → NIC DMA (복사 없음)
 *
 * 조건: 페이지가 읽기 전용이어야 함 (파일 시스템 페이지 캐시 = OK) */
setsockopt(sockfd, SOL_TLS, TLS_TX_ZEROCOPY_RO, &val, sizeof(val));

kTLS 하드웨어 오프로드

kTLS HW 오프로드는 TLS 레코드의 암호화/복호화를 NIC 하드웨어에서 수행합니다. CPU가 암호 연산을 전혀 수행하지 않아 100Gbps+ 환경에서도 라인 레이트 TLS를 달성할 수 있습니다.

오프로드 모드커널 설정동작CPU 부하
SW kTLS CONFIG_TLS=y 커널 CPU에서 AES-GCM 암호화/복호화 AES-NI 사용 시 적당 (코어당 ~10-20Gbps)
HW kTLS TX CONFIG_TLS_DEVICE=y NIC가 송신 데이터를 암호화 (TLS 레코드 생성) 암호화 CPU 0%
HW kTLS RX CONFIG_TLS_DEVICE=y NIC가 수신 데이터를 복호화 (TLS 레코드 해제) 복호화 CPU 0%
HW kTLS Record CONFIG_TLS_DEVICE=y NIC가 TLS 레코드 프레이밍까지 수행 TLS 전체 처리 CPU 0%
/* net/tls/tls_device.c — HW offload 설정 경로 */

int tls_set_device_offload(struct sock *sk, struct tls_context *ctx)
{
    struct net_device *netdev;
    struct tls_offload_context_tx *offload_ctx;

    /* 1. 소켓이 바인딩된 NIC 디바이스 확인 */
    netdev = get_netdev_for_sock(sk);

    /* 2. NIC가 kTLS offload를 지원하는지 확인 */
    if (!(netdev->features & NETIF_F_HW_TLS_TX))
        return -EOPNOTSUPP;

    /* 3. NIC 드라이버의 tls_dev_add 콜백 호출 */
    rc = netdev->tlsdev_ops->tls_dev_add(netdev, sk,
            TLS_OFFLOAD_CTX_DIR_TX, &ctx->crypto_send.info,
            tcp_sk(sk)->write_seq);
    /* → 드라이버: TLS 연결 정보(키, IV, seq)를 NIC HW에 설치
     * → NIC가 이 연결의 모든 TX 패킷을 자동 암호화
     *
     * 지원 NIC:
     * - Mellanox ConnectX-6 Dx / ConnectX-7: mlx5e_ktls_add_tx()
     * - Intel E810 (ICE): ice_tls_dev_add()
     * - Chelsio T6: cxgb4_ktls_dev_add()
     */

    /* 4. TCP prot을 HW offload용으로 교체 */
    tls_update_rx_zc_capable(ctx);
    ctx->tx_conf = TLS_HW;
}

/* include/net/tls.h — NIC 드라이버가 구현하는 TLS offload ops */
struct tlsdev_ops {
    int (*tls_dev_add)(struct net_device *netdev, struct sock *sk,
                       enum tls_offload_ctx_dir direction,
                       struct tls_crypto_info *crypto_info,
                       u32 start_offload_tcp_sn);
    void (*tls_dev_del)(struct net_device *netdev,
                        struct tls_context *ctx,
                        enum tls_offload_ctx_dir direction);
    int (*tls_dev_resync)(struct net_device *netdev,
                          struct sock *sk, u32 seq,
                          u8 *rcd_sn, enum tls_offload_ctx_dir direction);
};

/* NIC HW TX offload 패킷 전송 경로:
 *
 * tls_device_sendmsg() / tls_device_sendpage()
 *   → tls_push_data()        ← 데이터를 SG 리스트에 수집
 *   → tls_push_record()
 *     → tcp_sendmsg_locked()  ← 평문을 TCP로 전달
 *       → NIC TX 큐에 enqueue
 *         → NIC HW가 TLS 레코드 헤더 + 암호화 + 태그를 자동 생성
 *           → 와이어에 암호화된 TLS 레코드 전송
 *
 * CPU는 평문을 TCP에 전달만 → 암호화는 NIC가 라인 레이트로 처리
 */
# ━━━ kTLS HW Offload 상태 확인 ━━━

# NIC의 kTLS offload 지원 확인
ethtool -k eth0 | grep tls
# tls-hw-tx-offload: on      ← TX 오프로드 지원
# tls-hw-rx-offload: on      ← RX 오프로드 지원
# tls-hw-record: on          ← 레코드 프레이밍 오프로드

# kTLS offload 활성화/비활성화
ethtool -K eth0 tls-hw-tx-offload on
ethtool -K eth0 tls-hw-rx-offload on

# kTLS 통계 확인 (HW offload 카운터)
ethtool -S eth0 | grep tls
# tx_tls_encrypted_packets: 1234567
# tx_tls_encrypted_bytes: 987654321
# tx_tls_ooo: 0                    ← Out-of-order 패킷 (재전송)
# tx_tls_drop_no_sync_data: 0
# rx_tls_decrypted_packets: 654321
# rx_tls_decrypted_bytes: 543210987
# rx_tls_resync_req_pkt: 0         ← RX 동기화 재요청
# rx_tls_resync_req_start: 0
# rx_tls_resync_req_end: 0
# rx_tls_resync_res_ok: 0

# 커널 전역 kTLS 통계
cat /proc/net/tls_stat
# TlsCurrTxSw                  42    ← 현재 SW TX 연결 수
# TlsCurrRxSw                  38    ← 현재 SW RX 연결 수
# TlsCurrTxDevice               8    ← 현재 HW TX 연결 수
# TlsCurrRxDevice               6    ← 현재 HW RX 연결 수
# TlsTxSw                    5000    ← 누적 SW TX 연결
# TlsRxSw                    4500    ← 누적 SW RX 연결
# TlsTxDevice                1200    ← 누적 HW TX 연결
# TlsRxDevice                1000    ← 누적 HW RX 연결
# TlsDecryptError               0    ← 복호화 에러 (MAC 검증 실패 등)
# TlsRxDeviceResync             3    ← HW RX 재동기화 횟수
# TlsDecryptRetry               0
# TlsRxNoPadViolation           0    ← no_pad 위반

kTLS HW RX 재동기화 (Resync) 메커니즘

NIC RX 오프로드에서 TCP 재전송, 패킷 유실, 순서 변경이 발생하면 NIC의 TLS 레코드 시퀀스 번호와 실제 TCP 스트림이 불일치할 수 있습니다. 이때 커널과 NIC 사이의 재동기화(resync) 프로토콜이 동작합니다.

/* NIC RX offload 재동기화 흐름:
 *
 * 정상 상태:
 *   NIC가 수신 패킷의 TCP seq → TLS record seq 매핑을 유지
 *   → 패킷 도착 시 자동으로 복호화 후 커널에 전달
 *
 * 비정상 상태 (패킷 유실/재전송 등):
 *   NIC가 TLS 레코드 경계를 놓침 → 복호화 실패
 *
 *   1. NIC: 복호화 실패한 패킷을 암호문 상태로 커널에 전달
 *      (skb->decrypted = 0)
 *
 *   2. 커널 (tls_device.c):
 *      → SW fallback으로 해당 레코드 복호화
 *      → 정확한 TCP seq ↔ TLS record seq 매핑 계산
 *
 *   3. 커널 → NIC: tls_dev_resync() 호출
 *      → "TCP seq X부터 TLS record seq Y" 정보 전달
 *
 *   4. NIC: 새 매핑으로 HW 테이블 업데이트
 *      → 이후 패킷부터 다시 HW 복호화 재개
 *
 * resync 모드:
 * - TLS_OFFLOAD_SYNC_TYPE_DRIVER_REQ: 드라이버가 resync 요청
 * - TLS_OFFLOAD_SYNC_TYPE_CORE_NEXT_HINT: 커널이 힌트 제공
 */

/* mlx5 드라이버의 resync 구현 예시 */
/* drivers/net/ethernet/mellanox/mlx5/core/en_accel/ktls_rx.c */
static void mlx5e_ktls_rx_resync(struct net_device *netdev,
                                  struct sock *sk,
                                  u32 seq, u8 *rcd_sn)
{
    /* NIC HW의 TLS RX 컨텍스트를 새 시퀀스로 업데이트
     * → Flow Steering 규칙에 새 TCP seq/TLS seq 매핑 설치
     * → 다음 패킷부터 HW 복호화 재개 */
}

kTLS에서 TLS 1.2 vs TLS 1.3 차이

항목TLS 1.2TLS 1.3
kTLS 커널 지원 4.13+ (TX), 4.17+ (RX) 5.1+
Content Type 위치 레코드 헤더 (평문) 암호문 뒤 마지막 바이트 (inner content type)
레코드 헤더 실제 content type 포함 항상 APPLICATION_DATA(23)로 위장
AEAD nonce salt(4B) + explicit_iv(8B) = 12B salt(4B) XOR padded_seq(12B) = 12B
AAD 구성 seq_num(8B) + header(5B) = 13B header(5B) = 5B
패딩 없음 선택적 패딩 (content type 뒤에 0바이트 추가)
0-RTT 데이터 미지원 Early Data 지원 (kTLS에서는 유저스페이스에서 처리)
키 업데이트 재핸드셰이크 (kTLS와 비호환) KeyUpdate 메시지 (커널 6.0+에서 setsockopt으로 새 키 전달)
/* TLS 1.3 레코드 처리의 커널 구현 차이 */

/* net/tls/tls_sw.c — TLS 1.3 암호화 시 content type 처리 */
static void tls_fill_prepend(struct tls_context *ctx,
                            char *buf, size_t plaintext_len,
                            unsigned char record_type)
{
    /* TLS 1.2: 실제 content type을 헤더에 기록 */
    /* TLS 1.3: 헤더에 항상 APPLICATION_DATA(23) 기록
     *          실제 content type은 평문 끝에 추가 (inner content type)
     *
     * TLS 1.3 레코드 구조:
     * ┌───────────────┬─────────┬──────────┬────────────────┐
     * │ Header (5B)   │ Encrypt(│  Inner   │   AEAD Tag     │
     * │ type=23,ver,  │ Payload │ Content  │   (16B)        │
     * │ length        │         │ Type(1B) │                │
     * └───────────────┴─────────┴──────────┴────────────────┘
     * ← 평문 헤더 →   ← 암호화 영역 →
     *
     * 장점: 외부 관찰자가 content type을 알 수 없음 (프라이버시) */

    if (prot->version == TLS_1_3_VERSION) {
        buf[0] = TLS_RECORD_TYPE_DATA;  /* 항상 23 */
        /* plaintext 뒤에 실제 record_type 1바이트 추가 */
    } else {
        buf[0] = record_type;  /* 실제 타입 (23, 22, 21 등) */
    }
}

/* TLS 1.3 nonce 생성 (salt XOR seq) */
/* TLS 1.2: nonce = salt(4B) || explicit_iv(8B)
 * TLS 1.3: nonce = salt(4B padded to 12B) XOR seq_num(padded to 12B)
 *
 * TLS 1.3에서는 explicit IV가 전송되지 않아 레코드당 8바이트 절약 */

kTLS 커널 빌드 및 설정

# ━━━ 커널 빌드 옵션 ━━━

# kTLS 소프트웨어 지원 (필수)
CONFIG_TLS=y             # 또는 m (모듈)

# kTLS HW offload 지원 (선택)
CONFIG_TLS_DEVICE=y

# 관련 의존성
CONFIG_NET=y
CONFIG_INET=y
CONFIG_CRYPTO=y
CONFIG_CRYPTO_AEAD=y
CONFIG_CRYPTO_GCM=y      # AES-GCM 지원
CONFIG_CRYPTO_CHACHA20POLY1305=y  # ChaCha20-Poly1305 지원 (선택)
CONFIG_STREAM_PARSER=y   # strparser (TLS 레코드 파싱)

# ━━━ 모듈 로드 확인 ━━━
lsmod | grep tls
# tls                   126976  2

modinfo tls
# filename:  /lib/modules/.../net/tls/tls.ko
# description: Transport Layer Security Support
# license:  Dual BSD/GPL

# ━━━ OpenSSL kTLS 지원 확인 ━━━

# OpenSSL 3.0+에서 kTLS 활성화 확인
openssl version -a | grep KTLS
# OPENSSL_KTLS

# OpenSSL 빌드 시 kTLS 활성화
# ./Configure enable-ktls
# 런타임: SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS)

# GnuTLS: ktls 자동 감지 (3.7.2+)
# 커널 TLS 모듈이 로드되어 있으면 자동 사용

# ━━━ nginx kTLS 설정 ━━━

# nginx 1.21.4+ (OpenSSL 3.0+ 링크 필수)
# nginx.conf:
# http {
#     ssl_conf_command Options KTLS;
#     sendfile on;
#     ssl_protocols TLSv1.2 TLSv1.3;
# }

# ━━━ HAProxy kTLS 설정 ━━━

# HAProxy 2.6+ (OpenSSL 3.0+ 링크 필수)
# haproxy.cfg:
# global
#     ssl-engine ktls
#     # 또는 환경변수: OPENSSL_KTLS=1

kTLS 성능 특성

시나리오방식처리량 (단일 코어)CPU 사용률비고
정적 파일 서빙
(100KB 파일)
유저스페이스 TLS ~3-5 Gbps 100% read() + SSL_write()
kTLS SW sendfile ~8-12 Gbps 100% sendfile() 제로카피
kTLS HW offload ~25-40 Gbps ~20-30% NIC 암호화 (ConnectX-6 Dx)
동적 콘텐츠
(짧은 응답)
유저스페이스 TLS ~2-4 Gbps 100% send() + SSL_write()
kTLS SW ~3-5 Gbps 100% syscall 감소 효과 작음
대용량 전송
(1GB+ 파일)
kTLS SW sendfile ~15-20 Gbps 100% 대용량에서 제로카피 이점 극대
kTLS HW sendfile ~40-100 Gbps ~10-15% NIC 라인 레이트
kTLS 성능 주의사항:
  • 동적 콘텐츠에서의 이점 제한: kTLS의 주요 이점은 sendfile() 제로카피입니다. 동적으로 생성된 응답은 이미 유저스페이스 버퍼에 있으므로 제로카피 이점이 제한적. send()/sendmsg()에서는 syscall 오버헤드 감소와 유저-커널 경계 복사 1회 감소 정도
  • 소량 데이터 오버헤드: TLS 레코드 헤더(5B) + AEAD 태그(16B) = 최소 21바이트 오버헤드. 매우 작은 메시지에서는 비율이 높아질 수 있음
  • HW offload 연결 수 제한: NIC의 TLS 컨텍스트 테이블 크기에 따라 동시 오프로드 가능한 연결 수가 제한됨 (ConnectX-6 Dx: 수십만 연결). 초과 시 SW fallback
  • TCP 재전송과 HW offload: TCP 재전송 시 NIC가 이미 전송한 레코드를 재암호화해야 하며, 이때 커널과 NIC 사이 동기화가 필요 (성능 일시 저하)
  • renegotiation 비호환: TLS 1.2 renegotiation은 kTLS와 호환되지 않음. TLS 1.3 KeyUpdate는 커널 6.0+에서 지원

kTLS 디버깅 및 모니터링

# ━━━ kTLS 상태 모니터링 ━━━

# 1. 전역 kTLS 통계
cat /proc/net/tls_stat
# 주요 지표:
# TlsCurrTxSw / TlsCurrRxSw       — 현재 SW 모드 연결 수
# TlsCurrTxDevice / TlsCurrRxDevice — 현재 HW 모드 연결 수
# TlsDecryptError                   — 복호화 실패 (MAC 불일치)
# TlsRxDeviceResync                 — HW RX 재동기화 횟수

# 2. 소켓별 kTLS 상태 확인 (ss 도구)
ss -tni | grep -A1 "kTLS"
# ss -e 옵션으로 소켓 확장 정보에서 kTLS 모드 확인

# 3. NIC HW offload 카운터
ethtool -S eth0 | grep tls
# tx_tls_encrypted_packets / bytes — HW 암호화된 패킷/바이트
# tx_tls_ooo — out-of-order (재전송 등)
# rx_tls_decrypted_packets / bytes — HW 복호화된 패킷/바이트

# ━━━ ftrace / tracepoints ━━━

# kTLS 관련 tracepoints 확인
ls /sys/kernel/debug/tracing/events/tls/ 2>/dev/null || echo "TLS tracepoints 없음"

# 대안: kretprobe로 kTLS 함수 추적
bpftrace -e '
kprobe:tls_sw_sendmsg {
    printf("kTLS TX: pid=%d comm=%s size=%d\n",
           pid, comm, arg2);
}
kprobe:tls_sw_recvmsg {
    printf("kTLS RX: pid=%d comm=%s\n", pid, comm);
}
'

# kTLS 암호화 지연 측정
bpftrace -e '
kprobe:tls_do_encryption { @start[tid] = nsecs; }
kretprobe:tls_do_encryption /@start[tid]/ {
    @latency_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
'

# ━━━ 문제 해결 ━━━

# kTLS 활성화 실패 디버깅
dmesg | grep -i tls
# TLS module loaded
# 또는 에러: "kTLS offload not supported" 등

# OpenSSL kTLS 활성화 확인 (strace)
strace -e setsockopt nginx -t 2>&1 | grep SOL_TLS
# setsockopt(5, SOL_TLS, TLS_TX, ...) = 0  ← 성공
# setsockopt(5, SOL_TLS, TLS_TX, ...) = -1 ENOPROTOOPT ← TLS 모듈 미로드

# TLS 모듈이 로드되지 않은 경우
modprobe tls
# ENOPROTOOPT(92) → modprobe tls 후 재시도

# HW offload 실패 시 SW fallback 확인
cat /proc/net/tls_stat | grep -E "TlsCurr|TlsTx|TlsRx"
# TlsCurrTxDevice = 0이면 HW offload 미작동
# → ethtool -k eth0 | grep tls 로 NIC 지원 확인
# → NIC 드라이버가 tlsdev_ops를 구현했는지 확인
kTLS + BPF sockmap: BPF sockmap과 kTLS를 조합하면 커널 내에서 TLS 복호화 → BPF 프로그램 → 다른 소켓으로 전달을 유저스페이스 개입 없이 처리할 수 있습니다. 이는 L7 프록시(Envoy, Cilium)에서 커널 사이드카 가속에 활용됩니다. bpf_msg_redirect_map()을 사용하여 kTLS 소켓 간 데이터를 커널 내에서 직접 전달합니다.

kTLS HW Offload 지원 NIC

NIC 벤더제품TX OffloadRX Offload커널 드라이버최소 커널
NVIDIA/Mellanox ConnectX-6 Dx / ConnectX-7 TLS 1.2/1.3 TLS 1.2/1.3 mlx5_core 5.3+ (TX), 5.8+ (RX)
Intel E810 (ICE) TLS 1.2/1.3 ice 5.14+
Chelsio T6 TLS 1.2 cxgb4 5.3+
Broadcom NetXtreme-E (BCM5750X) TLS 1.2 bnxt_en 5.8+
Netronome Agilio SmartNIC TLS 1.2 nfp 5.2+
NETIF_F_HW_TLS 피처 플래그:
  • NETIF_F_HW_TLS_TX — NIC가 TX TLS offload를 지원 (ethtool -k에서 tls-hw-tx-offload)
  • NETIF_F_HW_TLS_RX — NIC가 RX TLS offload를 지원 (tls-hw-rx-offload)
  • NETIF_F_HW_TLS_RECORD — NIC가 TLS 레코드 프레이밍까지 수행 (tls-hw-record)
  • NIC가 지원하지 않으면 자동으로 SW kTLS로 fallback (유저에게 투명)