전체 흐름
net_ns_init()은 패킷을 NIC에서 socket으로 전달하는 함수가 아니다. 부팅 중 기본 network namespace인 init_net을 준비하고 이미 등록된 pernet 작업을 그 namespace에 실행한다.
- start_kernelnet_ns_init 직접 호출
- 전역 기반선택적 net_cachep/netns_wq, init_net.gen
- init_net 설정preinit_net → setup_net → 목록 공개
- 후속 등록net_ns_ops; 나중에 net_dev_init의 loopback pernet
마지막 칸은 같은 함수 본문 내부의 연속 단계가 아니다. net_dev_init()은 별도 subsys_initcall이며 loopback pernet 등록 시 기존 init_net에도 콜백을 실행한다.
잠금 안에서 preinit_net(&init_net) 뒤 setup_net(&init_net, &init_user_ns). setup_net 성공 후 init_net_initialized가 true가 되고 이어 net_ns_ops가 등록된다.
소스 코드 위치
| 확인 지점 | 고정 소스 범위 | 확인할 사실 |
|---|---|---|
| 부팅 진입 | init/main.c 1051 | start_kernel()에서 직접 호출 |
| 기본 namespace | net_namespace.c 1102–1141; 317–370 | 전역 준비, preinit/setup 및 공개 순서 |
| 새 namespace | net_namespace.c 462–507 | CLONE_NEWNET 분기와 반환 오류 |
| pernet 등록·정리 | net_namespace.c 1152–1178; 566–636 | 기존 namespace에 콜백 적용 및 역순 정리 |
| loopback | net/core/dev.c 11511–11587 | 별도 net_dev_init()에서 pernet device 등록 |
행 번호는 사이트의 v6.6 소스 사본 기준이다. 해당 태그의 원본 파일과 고정 사본을 함께 확인한다.
대표 코드
net_ns_init
void __init net_ns_init(void)
{
struct net_generic *ng;
#ifdef CONFIG_NET_NS
net_cachep = kmem_cache_create("net_namespace", sizeof(struct net),
SMP_CACHE_BYTES,
SLAB_PANIC|SLAB_ACCOUNT, NULL);
/* Create workqueue for cleanup */
netns_wq = create_singlethread_workqueue("netns");
if (!netns_wq)
panic("Could not create netns workq");
#endif
ng = net_alloc_generic();
if (!ng)
panic("Could not allocate generic netns");
rcu_assign_pointer(init_net.gen, ng);
#ifdef CONFIG_KEYS
init_net.key_domain = &init_net_key_domain;
#endif
down_write(&pernet_ops_rwsem);
preinit_net(&init_net);
if (setup_net(&init_net, &init_user_ns))
panic("Could not setup the initial network namespace");
init_net_initialized = true;
up_write(&pernet_ops_rwsem);
if (register_pernet_subsys(&net_ns_ops))
panic("Could not register network namespace subsystems");
rtnl_register(PF_UNSPEC, RTM_NEWNSID, rtnl_net_newid, NULL,
RTNL_FLAG_DOIT_UNLOCKED);
rtnl_register(PF_UNSPEC, RTM_GETNSID, rtnl_net_getid, rtnl_net_dumpid,
RTNL_FLAG_DOIT_UNLOCKED);
}
초기 network namespace용 기반을 준비하고 init_net을 설정한다. CONFIG_NET_NS 등의 전처리 조건에 따라 cache와 cleanup workqueue 준비가 달라진다. setup_net()과 pernet subsystem 등록의 실패는 초기 부팅 중 panic 경로로 이어지며, 일반적인 namespace 생성 요청의 반환 오류와 다르다.
setup_net
static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
{
/* Must be called with pernet_ops_rwsem held */
const struct pernet_operations *ops, *saved_ops;
int error = 0;
LIST_HEAD(net_exit_list);
refcount_set(&net->ns.count, 1);
ref_tracker_dir_init(&net->refcnt_tracker, 128, "net refcnt");
refcount_set(&net->passive, 1);
get_random_bytes(&net->hash_mix, sizeof(u32));
preempt_disable();
net->net_cookie = gen_cookie_next(&net_cookie);
preempt_enable();
net->dev_base_seq = 1;
net->user_ns = user_ns;
idr_init(&net->netns_ids);
spin_lock_init(&net->nsid_lock);
mutex_init(&net->ipv4.ra_mutex);
list_for_each_entry(ops, &pernet_list, list) {
error = ops_init(ops, net);
if (error < 0)
goto out_undo;
}
down_write(&net_rwsem);
list_add_tail_rcu(&net->list, &net_namespace_list);
up_write(&net_rwsem);
out:
return error;
out_undo:
/* Walk through the list backwards calling the exit functions
* for the pernet modules whose init functions did not fail.
*/
list_add(&net->exit_list, &net_exit_list);
saved_ops = ops;
list_for_each_entry_continue_reverse(ops, &pernet_list, list)
ops_pre_exit_list(ops, &net_exit_list);
synchronize_rcu();
ops = saved_ops;
list_for_each_entry_continue_reverse(ops, &pernet_list, list)
ops_exit_list(ops, &net_exit_list);
ops = saved_ops;
list_for_each_entry_continue_reverse(ops, &pernet_list, list)
ops_free_list(ops, &net_exit_list);
rcu_barrier();
goto out;
}
확인 사항: setup_net은 pernet 목록을 순서대로 초기화한다. 실패하면 성공한 콜백만 역순 정리하고 namespace 목록에 추가하지 않는다. 버전별 정리 방식은 아래 표에서 비교한다.
함수별 분석
두 경로 모두 pernet 목록과 초기화 순서를 조정하지만 부팅 panic과 런타임 오류 반환은 구별한다.
| 함수 | 정확한 상태 변화 | 실패 또는 경계 |
|---|---|---|
| net_ns_init | 잠금 안에서 preinit_net(&init_net) 뒤 setup_net(&init_net, &init_user_ns); init_net_initialized 뒤 net_ns_ops 등록 | 핵심 실패는 부팅 panic |
| setup_net | pernet_list 순서대로 ops_init; 모두 성공하면 net_namespace_list에 추가 | 성공한 ops를 역순으로 pre_exit → synchronize_rcu → exit → free 직접 호출 |
| register_pernet_subsys | 쓰기 잠금 아래 ops를 목록에 더하고 기존 각 net에도 ops_init 적용 | 등록 실패 시 목록 제거와 이미 적용한 net 정리 |
| copy_net_ns | CLONE_NEWNET이면 새 net을 할당하고 setup_net 호출; 없으면 get_net(old_net) | quota/할당/잠금/콜백 실패는 ERR_PTR |
구조체와 필드
| 객체·필드 | 역할과 소유 관계 | 관찰 지점 |
|---|---|---|
| init_net / struct net | 정적 기본 객체; 새 net은 net_alloc으로 별도 확보 | 부팅 객체와 런타임 생성 객체를 구별 |
| init_net.gen | net_alloc_generic 결과를 RCU 포인터로 연결 | pernet generic 저장소 기반 |
| pernet_list / pernet_operations | subsystem별 init, pre_exit, exit와 선택적 id/size | setup_net은 등록 순서로 init, 정리는 역순 |
| pernet_ops_rwsem | 등록은 쓰기 잠금, 새 net 설정과 cleanup은 읽기 잠금 | 등록·생성·정리 순서 조정 |
| net_namespace_list | 성공한 net을 RCU 목록에 연결 | setup 실패 이전에는 공개하지 않음 |
netns id는 모든 생성의 마지막 고정 단계가 아니다. per-netns ID 매핑은 별도 요구에 따라 할당된다.
실행 단계와 실패 정리
- 객체 preinit수명·ID·잠금 등 기반 준비
- pernet init 순회ops_init를 목록 순서로 실행
- 성공namespace 목록에 공개
- 실패성공한 콜백만 역순 정리, 미공개
성공한 ops를 역순으로 pre_exit → synchronize_rcu → exit → free 직접 호출. 등록 실패를 되돌리는 경로와 마지막 참조 뒤 cleanup_net이 실행되는 종료 경로를 구별한다.
| 경계 | 성공 시 | 실패·종료 시 |
|---|---|---|
| 부팅 net_ns_init | init_net_initialized 후 net_ns_ops 등록 | 초기 설정 불가 시 panic |
| setup_net | net_namespace_list에 추가 | 성공한 ops를 역순으로 pre_exit → synchronize_rcu → exit → free 직접 호출 |
| 런타임 copy_net_ns | 새 net 또는 기존 net 참조 반환 | quota, 할당, 잠금, 콜백 오류는 ERR_PTR |
| 마지막 참조 이후 | cleanup workqueue가 종료 처리 | 역순 pernet pre_exit/exit/free와 RCU 대기 뒤 net_free |
| loopback | 나중의 net_dev_init에서 register_pernet_device | net_ns_init가 loopback을 직접 up으로 만드는 단계는 아님 |
불변 조건과 실패 사례
| 조건 | 근거 | 오해하면 생기는 문제 |
|---|---|---|
| 공개 전 콜백 완료 | setup_net은 모든 pernet init 성공 후 목록에 추가 | 부분 초기화된 net을 성공으로 취급 |
| 등록과 생성 순서 | pernet_ops_rwsem의 쓰기/읽기 잠금 | 새 pernet ops 적용 범위 누락 |
| 역순 정리와 RCU 대기 | 성공한 ops를 역순으로 pre_exit → synchronize_rcu → exit → free 직접 호출; 역순 pernet pre_exit/exit/free와 RCU 대기 뒤 net_free | 콜백 해제와 RCU 독자의 경합 |
| init_net은 별도 기본 객체 | 정적 init_net과 copy_net_ns 할당 객체가 다름 | 프로세스 lifetime이나 netns id와 동일시 |
| loopback 등록 시점 | net_dev_init은 별도 subsys_initcall | net_ns_init가 loopback 상태를 직접 올린다고 해석 |
계측과 검증
부팅의 net_ns_init는 이미 지난 뒤 런타임 ftrace를 켜도 다시 실행되지 않는다. 실행 순서는 소스와 부팅 시점 추적으로 확인하고 새 namespace 경로는 실험 환경에서 별도로 관찰한다.
| 관찰 | 증명할 수 있는 것 | 증명하지 못하는 것 |
|---|---|---|
| 고정 소스의 호출·분기 | net_ns_init, setup_net, register_pernet, cleanup의 정적 순서 | 특정 장치의 실제 구성 옵션 |
| /proc/self/ns/net 또는 ip netns | 실행 중 namespace 정체성과 새 net의 존재 | 부팅 당시 pernet 콜백 순서 |
| copy_net_ns/setup_net 함수 추적 | 추적을 켠 뒤 발생한 CLONE_NEWNET 경로 | 이미 완료된 net_ns_init 부팅 호출 |
| /proc/net/dev | 현재 netns에서 보이는 netdevice | loopback 등록이 net_ns_init 안에서 일어났다는 주장 |
추가 확인 사항
- preinit_net과 setup_net이 각각 어떤 필드를 준비하고 언제 pernet_ops_rwsem을 잡는가?
- 기존 init_net에 나중에 등록한 pernet_operations의 init은 어디서 실행되는가?
- setup_net의 어느 줄 뒤에야 새 net이 목록에 공개되며 콜백 실패 시 무엇을 되돌리는가?
- copy_net_ns의 CLONE_NEWNET 없는 분기와 있는 분기의 수명 규칙은 어떻게 다른가?
- loopback pernet 등록은 net_ns_init 본문인가 별도 initcall인가?