같은 증상으로 검색해 오신 분들께
증상: IPsec dialup을 TCP transport로 설정했을 때 약 50% 확률로 접속 실패. TCP 3-way handshake는 성립하고, 약 15초 무응답 후 RST. IKE 로그에는 IKE SA: created 1/1 / IPsec SA: created 0/0으로 남고 FortiClient UID는 공란. 환경: FortiGate 201F / FortiOS 7.4.11 / FortiClient 7.4.3 VPN-only / IKEv2 + EAP-TTLS 결론: 설정 계층에서 해결되지 않았다. IKE 프래그먼트가 여러 TCP 세그먼트에 걸쳐 도착할 때 재조합이 실패하는 것으로 관측됐고, 우리가 시도한 모든 설정 조정은 발생 확률만 바꿨다. 당장 할 수 있는 것: UDP를 기본 프로파일로, TCP를 백업 프로파일로 이원화. 이게 실질적으로 유일한 실용적 대응이었다. 중요: 커뮤니티에서 가장 많이 추천되는 set auto-asic-offload disable은 우리 환경에서 효과가 없었다. 하지만 다른 사람들에게는 효과가 있었다. 아래에서 이 차이를 다룬다. 검색 키워드: IKE SA: created 1/1 IPsec SA: created 0/0 IPsec over TCP not working wrong transport phase 1 uses non udp fragmentation-mtu auto-asic-offload
1. 증상 — 무엇이 어떻게 실패했는가
같은 노트북, 같은 회선, 같은 프로파일로 연속 시도했는데 결과가 갈렸다.
| 실패율 | 약 50% (반복 시도 기준) |
| 실패 양상 | TCP handshake 성립 → 약 15초 무응답 → RST |
| FortiClient UID | 약 36초 경과 후에도 공란 |
| IKE 로그 종료 상태 | IKE SA: created 1/1 / IPsec SA: created 0/0 |
| 재현 조건 | 네트워크 환경 종속. 사내망에서는 낮고 외부망에서 높음 |
| 영향 | 재시도 1~2회로 접속되므로 장애 신고는 적었으나, 외부 현장에서 신뢰 문제 |
절반이 성공한다는 게 가장 나쁜 조건이었다.
완전히 안 되면 원인 찾기가 쉽다. 절반이 되면 "그 회선이 원래 좀 그래서"라는 설명이 계속 그럴듯하게 들린다. 그리고 사용자들도 재시도로 해결하니까 문의를 안 한다. 문제가 조직 내에서 보이지 않는 상태로 유지된다.
실제로 나는 이 현상을 몇 주 동안 "외부망 품질 문제"로 분류하고 있었다. Part 6에서 구버전 프로파일이 TCP로 붙은 적이 없다는 걸 알게 된 뒤에야, 이게 독립적인 문제라는 걸 인식했다.
2. 영향 — 사용자에게 무슨 일이 일어났는가
기술적 영향: TCP transport 사용 시 초기 접속 시도의 약 절반이 15초 타임아웃 후 실패. 재시도 시 성공률은 동일하게 50% 수준이므로, 기대 시도 횟수는 2회, 최악의 경우 4~5회까지 갔다.
업무 영향: 여기가 실제 문제였다.
UDP가 막힌 환경에서만 TCP 프로파일이 필요하다. 그런데 UDP가 막힌 환경은 대체로 급한 상황이다. 정부 부처 기자실, 행사장 공용망, 호텔. 기사 마감을 앞두고 사내 시스템에 접속해야 하는데 두세 번 실패하면, 그 사용자에게 이 시스템은 "안 되는 시스템"이 된다.
장애 티켓 수로는 측정되지 않는 종류의 손실이었다. 재시도로 우회 가능한 실패는 통계에 안 잡히고 신뢰에만 잡힌다.
3. 처음 세운 가설과, 그것이 왜 틀렸는가
첫 가설: RADIUS 응답 지연 또는 EAP-TTLS 인증 실패.
합리적인 출발점이었다. 인증 체인이 가장 길고, TOTP 스크립트가 끼어 있고, 무엇보다 Part 5에서 실제로 인증 계층에서 다섯 개의 문제를 잡았기 때문이다.
이게 정확히 함정이었다.
직전 경험이 다음 가설을 편향시킨다.
방금 인증 계층에서 다섯 개를 찾았으니, 여섯 번째도 거기 있을 것 같았다. 그래서 FreeRADIUS 로그를 뒤지고, TOTP 스크립트에 타이밍 로그를 넣고, radiusd -X를 며칠 켜뒀다.
그리고 여기서 Part 2에서 이미 배운 함정을 다시 밟았다. radtest로 검증하려 했는데 응답이 없었다. TOTP 구성 상태에서는 유효한 OTP 접미사 없이 던지면 스크립트가 입력을 기다리며 멈춘다는 걸 알고 있었는데도, 급한 마음에 잊었다. 그리고 "RADIUS가 응답을 안 한다"는 잘못된 관측을 30분 정도 사실로 믿었다.
같은 함정을 두 번 밟는 게 이상한 일이 아니다. 압박 상황에서는 최근에 배운 것보다 오래된 습관이 먼저 나온다. 그래서 나는 이 함정을 진단 절차 문서의 맨 위에 적어놨다.
4. 관측 — 로그가 실제로 말한 것
가설을 폐기시킨 것은 IKE 로그였다.
diagnose vpn ike log filter clear
# 주의: rem-addr4 뒤는 공백이다. 하이픈이 아니다.
diagnose vpn ike log filter rem-addr4 <클라이언트공인IP>
diagnose debug application ike -1
diagnose debug enable
실패한 세션은 이렇게 끝났다.
IKE SA: created 1/1
IPsec SA: created 0/0
transport: TCP
이 세 줄이 결정적이었다.
IKE SA: created 1/1은 IKE 협상이 완료됐다는 뜻이다. IKEv2에서 IKE SA가 세워졌다면 IKE_AUTH 교환이 끝났다는 것이고, 그건 인증이 통과했다는 뜻이다. 인증이 실패했다면 여기가 0/0이거나 EAP 실패 메시지가 남는다.
그런데 IPsec SA: created 0/0. 인증 후 실제 데이터 터널을 설치하는 단계에서 죽고 있었다.
즉 RADIUS는 무죄였다. 며칠을 잘못된 계층에서 태운 것이다.
이 로그 패턴의 진단 가치
이 조합을 정리해두면 다음에 훨씬 빠르다.
| 0/0 | 0/0 | IKE 협상 실패. 제안(proposal) 불일치, PSK 불일치, 도달 불가 |
| 1/1 | 0/0 | 인증 통과, 터널 설치 실패. Phase2 부재 또는 전송 계층 문제 |
| 1/1 | 1/1 | 정상. 이후 문제는 정책/라우팅 계층 |
1/1 / 0/0을 봤을 때 인증 계층을 뒤지는 건 시간 낭비다. 이걸 Part 5 시점에 알았다면 며칠을 아꼈다.
5. 좁혀나가기 — 가설, 실험, 결과
여기서부터는 배제 작업이었다. 커뮤니티에서 보고된 해결책을 하나씩 적용했다.
| 1 | 하드웨어 오프로드 간섭 | set npu-offload disable (phase1) | 변화 없음 | 배제 |
| 2 | 정책 단위 ASIC 오프로드 | 관련 정책에 set auto-asic-offload disable | 변화 없음 | 배제 (아래 상세) |
| 3 | IKE 프래그먼트 크기 과다 | fragmentation-mtu 1200 → 900 | 부분 개선, 불안정 | 방향은 맞음, 해결 아님 |
| 4 | TCP 폴백 임계값 과민 | fallback-tcp-threshold 조정 | 실패율 양상만 변동 | 증상 이동, 해결 아님 |
| 5 | 클라이언트 DH 그룹 | 클라이언트/서버 모두 DH 14 이상 고정 | 변화 없음 (이미 충족) | 배제 |
| 6 | 중복 가상 어댑터 | 장치 관리자에서 잔여 Fortinet 어댑터 제거 | 일부 장비에서 개선 | 별개 문제였다 (Part 9) |
| 7 | TCP 세그먼트 병합 시 재조합 실패 | 성공/실패 세션 패킷 캡처 비교 | 병합된 세그먼트에서만 실패 | 여기서 멈췄다 |
실험 2가 중요하다 — 가장 유명한 해결책이 통하지 않았다
커뮤니티에서 IPsec-over-TCP 문제에 가장 많이 추천되는 조치가 이거다.
config firewall policy
edit <VPN 정책번호>
set auto-asic-offload disable
next
end
근거도 명확하다. IPsec over TCP는 NPU 오프로드와 ADVPN을 지원하지 않는다고 여러 문서에 기재되어 있고, 실제로 이걸 끄자마자 IKE 디버그에 아무것도 안 나오던 상태에서 협상이 진행되기 시작했다는 보고가 있다.
우리는 이걸 적용했다. 효과가 없었다.
여기서 두 가지 중 하나다.
- 우리 문제는 오프로드와 무관한 다른 원인이다
- 우리는 이미 오프로드가 문제되지 않는 상태였다
2번을 확인했다. npu-offload를 이미 끈 상태였고, 정책에도 적용했고, 그런데도 실패율이 그대로였다. 따라서 우리 문제는 오프로드와 무관한 별개 원인이었다.
이 실험 결과 자체가 이 글의 가치 절반이다. "이 조치로 해결됐습니다"만 쓰인 스레드를 읽고 적용했는데 안 되면, 자기 문제가 그 문제와 다르다는 것조차 판단하기 어렵다. 통하지 않은 조치를 기록하는 것이 통한 조치를 기록하는 것만큼 유용하다.
실험 3이 함정이었다 — "부분 개선"의 위험성
fragmentation-mtu를 1200에서 900으로 내렸더니 실패율이 눈에 띄게 떨어졌다.
이 순간 나는 원인을 찾았다고 생각했다. 개선이 보이면 그 방향을 계속 파고 싶어진다. 그래서 값을 더 내려보고, 다른 파라미터와 조합해보고, 며칠을 더 썼다.
그런데 안정되지 않았다. 실패율이 낮아졌을 뿐 0이 되지 않았고, 환경에 따라 다시 올라갔다.
여기서 깨달은 게 이 편의 핵심 교훈이다.
"부분 개선"은 원인을 맞췄다는 신호가 아니다. 원인을 회피할 확률이 올라갔다는 신호일 수 있다.
프래그먼트를 작게 만들면 여러 세그먼트에 걸칠 확률이 줄어든다. 재조합 로직이 고쳐진 게 아니라, 재조합이 필요한 상황이 덜 발생하게 된 것이다. 원인은 그대로 있었다.
Part 6에서 fallback-tcp-threshold를 조정했을 때 "증상이 사라지지 않고 이동했다"는 관찰을 해뒀던 게 여기서 도움이 됐다. 같은 패턴이었다.
구별 기준: 조치 후 실패율이 0이 되지 않고 낮아지기만 한다면, 대체로 회피이지 해결이 아니다.
실험 7 — 패킷 캡처 비교
마지막으로 성공 세션과 실패 세션을 나란히 캡처해서 비교했다.
# 왜: TCP 4500 양방향. 4는 헤더+페이로드 hex, 0은 무한, l은 로컬시각
diagnose sniffer packet port1 'tcp port 4500 and host <클라이언트IP>' 4 0 l
패턴이 보였다. 실패한 세션에서는 IKE 메시지가 여러 TCP 세그먼트에 걸쳐 도착했고, 그 세그먼트들이 병합된 형태로 전달됐다. 성공한 세션에서는 그렇지 않았다.
이걸 아는 것이 왜 자연스러운가에 대한 배경도 문서에 있다. IKE는 원래 UDP를 전제로 설계됐고, 인증서 체인 같은 큰 페이로드가 들어가면 메시지가 커진다. 그래서 IKE 프래그멘테이션이 존재한다. IKE를 TCP 위에 태우면 여기에 "TCP는 스트림이고 메시지 경계를 보존하지 않는다"는 조건이 추가된다. 즉 애플리케이션 계층에서 메시지 경계를 다시 세워야 하는데, 이 재조합 지점이 이 문제의 위치로 관측됐다.
관련해서 하드웨어 쪽 문서도 확인했다. NP6 계열은 ingress 방향의 프래그먼트 재조합을 지원하지 않고 조각을 CPU로 넘긴다. NP7은 패킷당 2개 조각까지 하드웨어에서 처리하고 그 이상은 CPU로 간다. 본인 모델의 NP 세대를 확인해두면 이 계열 문제를 판단할 때 도움이 된다.
6. 근본 원인 — 어디까지 확실하고 어디부터 추정인가
정직하게 구분해서 쓴다.
관측된 사실
- 실패는 TCP transport에서만 발생한다. UDP에서는 발생하지 않는다.
- 실패 시 IKE SA는 생성되고 IPsec SA는 생성되지 않는다.
- 실패 세션에서는 IKE 메시지가 병합된 다중 TCP 세그먼트로 도착한다.
- 프래그먼트 크기를 줄이면 실패율이 감소하지만 0이 되지 않는다.
- 우리가 시도한 모든 설정 변경은 실패율을 바꿨을 뿐 제거하지 못했다.
추정 (벤더 확인 전)
- 병합된 TCP 세그먼트에 걸친 IKE 프래그먼트의 재조합이 실패하고 있다.
- 이는 설정으로 조정 가능한 계층이 아니다.
이 구분이 중요한 이유: "펌웨어 버그"라고 단정하면 읽는 사람이 자기 문제도 같다고 결론 내리고 진단을 멈출 수 있다. 실제로는 같은 증상에 여러 원인이 있고, 실험 2가 그걸 보여준다. 그래서 관측과 추정을 나눠 쓴다.
벤더 기술지원으로 에스컬레이션했다. 진행 상황은 이 글에 추가한다.
7. 조치 — 실제로 무엇을 바꿨는가
적용한 것: 프로파일 이원화
<connections>
<connection>
<name>Corp-VPN</name>
<description>기본 (평소 이걸 사용)</description>
<transport_mode>0</transport_mode> <!-- UDP -->
</connection>
<connection>
<name>Corp-VPN-Alt</name>
<description>기본이 안 될 때</description>
<transport_mode>1</transport_mode> <!-- TCP -->
</connection>
</connections>
이게 해결이 아니라 운영 전략임을 분명히 한다. TCP 경로의 문제는 그대로 있다. 다만:
- UDP가 통하는 대다수 환경에서는 문제를 만나지 않는다
- UDP가 막힌 환경에서는 재시도로 접속된다 (2회 기대)
- 사용자 안내는 한 줄로 유지된다
유지한 완화책
config vpn ipsec phase1-interface
edit "ra-ipsec"
set fragmentation enable
# 왜: 근본 해결은 아니지만 TCP 경로 실패율을 낮춘다
set fragmentation-mtu 900
set npu-offload disable
next
end
fragmentation-mtu 900은 회피책임을 알면서 유지한다. 근본 원인은 그대로지만 실패 확률이 낮아지는 건 사실이고, 대가는 프래그먼트 개수 증가로 인한 약간의 오버헤드뿐이다.
주의: 이 값은 우리 환경 기준이다. 무조건 900을 권하지 않는다. 그리고 이 값이 설정 리로드나 펌웨어 작업 후 되돌아가 있는 경우가 있었다. 정기적으로 확인한다.
bashget vpn ipsec phase1-interface ra-ipsec | grep -E 'fragmentation|transport|npu'
적용하지 않기로 한 것
| FortiOS 7.4.12로 업그레이드 | 릴리즈 노트에 이 문제에 대한 명시적 수정 항목이 없음 |
| FortiOS 7.6.x로 업그레이드 | IKEv2 관련 별개 회귀 이슈가 보고됨 (Part 12) |
| TCP를 기본으로 유지 | 다수 사용자를 불안정 경로에 두는 선택 |
| 사용자에게 재시도 안내만 하기 | 이건 방치다 |
8. 아직 해결되지 않은 것
- TCP 경로의 근본 원인은 미해결이다. 벤더 대응 진행 중.
- UDP 프로파일도 일부 환경에서 실패하는 사례가 있다. TCP는 되는데 UDP는 안 되는 환경도 존재하고, 이건 예상 범위(경로상 UDP 차단)이지만 사례 수집이 더 필요하다.
- 이원화 전략의 실제 효과를 정량적으로 측정하지 못했다. "문의가 줄었다" 수준이다.
이 항목들을 남겨두는 이유가 있다. "모두 해결했습니다"로 끝나는 글보다 이런 글이 오래 읽힌다. 같은 문제를 겪는 사람이 필요한 건 완결된 성공담이 아니라, 어디까지 확인됐고 어디가 미지인지에 대한 정확한 지도다.
9. 여기서 배운 것
- IKE SA: created 1/1 + IPsec SA: created 0/0은 인증 통과 후 터널 설치 실패를 뜻한다. 이 조합을 봤을 때 인증 계층을 뒤지면 안 된다.
- 직전 경험이 다음 가설을 편향시킨다. 방금 인증 계층에서 문제를 찾았으면 다음 문제도 거기 있을 것 같아진다. 로그가 다른 곳을 가리키면 직관보다 로그를 믿는다.
- "부분 개선"은 해결의 신호가 아니라 회피의 신호일 수 있다. 실패율이 0이 되지 않고 낮아지기만 하면 원인은 그대로 있다.
- 파라미터를 바꿨을 때 증상이 사라지지 않고 이동하면, 원인을 못 찾은 것이다.
- 통하지 않은 조치를 기록한다. 남들에게 효과 있던 조치가 나에게 없었다는 사실이, 내 문제가 다른 문제라는 가장 강한 증거였다.
- 관측과 추정을 문서에서 분리한다. 이 구분이 없으면 읽는 사람이 자기 문제를 오진하게 된다.
- 진단 절차 문서의 맨 위에 본인이 이미 밟은 함정을 적어둔다. 압박 상황에서는 최근에 배운 것보다 오래된 습관이 먼저 나온다.
부록: 사용한 진단 커맨드
# IKE 로그 (특정 클라이언트만)
diagnose vpn ike log filter clear
diagnose vpn ike log filter rem-addr4 <클라이언트공인IP> # 공백, 하이픈 아님
diagnose debug application ike -1
diagnose debug enable
# 게이트웨이/세션 상태 — transport가 실제로 무엇인지 확인
diagnose vpn ike gateway list | grep -E 'name:|transport:|created'
# 터널 SA
diagnose vpn tunnel list | grep -A5 ra-ipsec
# 패킷 캡처 (TCP transport)
diagnose sniffer packet port1 'tcp port 4500 and host <클라이언트IP>' 4 0 l
# 현재 적용값 확인 (되돌아가 있는지)
get vpn ipsec phase1-interface ra-ipsec | grep -E 'fragmentation|transport|npu|fallback'
# 정리
diagnose debug disable
diagnose debug reset
다음 편에서는
Part 9에서 완전히 다른 종류의 원인을 다룬다. 서버 설정을 아무리 봐도 나오지 않았던 것. 문제는 VPN이 아니라 무선 랜 드라이버였다.
'네트워크' 카테고리의 다른 글
| 하나의 게이트웨이에 PSK 터널 여러 개 붙이기 — network-id와 보조 공인 IP (Part 10) (0) | 2026.07.26 |
|---|---|
| VPN이 아니라 무선 드라이버였다 — 서버만 보다가 놓친 것 (Part 9) (1) | 2026.07.26 |
| 다시 한다면 무엇을 바꿀까 — 그리고 아직 끝나지 않은 것들 (Part 12) (0) | 2026.07.25 |
| authusrgrp를 쓰면 방화벽 정책의 그룹 매칭이 죽는다 — 문서에 없던 상호작용 (Part 7) (0) | 2026.07.25 |
| transport_mode="tcp"는 조용히 무시된다 — 우연히 동작했던 프로파일의 정체 (Part 6) (0) | 2026.07.25 |