Network · Linux v6.6 · source + diagram note

net_ns_init

Linux v6.6: 기본 network namespace의 부팅 설정과 이후 새 namespace 생성의 경계를 읽는다.

전체 흐름

net_ns_init()은 패킷을 NIC에서 socket으로 전달하는 함수가 아니다. 부팅 중 기본 network namespace인 init_net을 준비하고 이미 등록된 pernet 작업을 그 namespace에 실행한다.

그림 1. 부팅 초기화와 뒤따르는 pernet 등록 (v6.6)
  1. start_kernelnet_ns_init 직접 호출
  2. 전역 기반선택적 net_cachep/netns_wq, init_net.gen
  3. init_net 설정preinit_net → setup_net → 목록 공개
  4. 후속 등록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 1051start_kernel()에서 직접 호출
기본 namespacenet_namespace.c 1102–1141; 317–370전역 준비, preinit/setup 및 공개 순서
새 namespacenet_namespace.c 462–507CLONE_NEWNET 분기와 반환 오류
pernet 등록·정리net_namespace.c 1152–1178; 566–636기존 namespace에 콜백 적용 및 역순 정리
loopbacknet/core/dev.c 11511–11587별도 net_dev_init()에서 pernet device 등록

행 번호는 사이트의 v6.6 소스 사본 기준이다. 해당 태그의 원본 파일과 고정 사본을 함께 확인한다.

대표 코드

net_ns_init

원본 코드: net/core/net_namespace.c:1102-1141 · v6.6 · 로컬 원문
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

원본 코드: net/core/net_namespace.c:317-370 · v6.6 · 로컬 원문
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 목록에 추가하지 않는다. 버전별 정리 방식은 아래 표에서 비교한다.

함수별 분석

그림 2. 초기화와 이후 생성은 서로 다른 경로
부팅: net_ns_init전역 준비 → init_net preinit/setup → init_net_initialized → net_ns_ops 등록. setup 또는 등록 실패는 panic.
런타임: copy_net_nsCLONE_NEWNET 없음: 기존 net 참조 증가. 있음: quota → net_alloc → preinit/setup → 새 net 반환; 실패 시 ERR_PTR과 자원 정리.

두 경로 모두 pernet 목록과 초기화 순서를 조정하지만 부팅 panic과 런타임 오류 반환은 구별한다.

함수정확한 상태 변화실패 또는 경계
net_ns_init잠금 안에서 preinit_net(&init_net) 뒤 setup_net(&init_net, &init_user_ns); init_net_initialized 뒤 net_ns_ops 등록핵심 실패는 부팅 panic
setup_netpernet_list 순서대로 ops_init; 모두 성공하면 net_namespace_list에 추가성공한 ops를 역순으로 pre_exit → synchronize_rcu → exit → free 직접 호출
register_pernet_subsys쓰기 잠금 아래 ops를 목록에 더하고 기존 각 net에도 ops_init 적용등록 실패 시 목록 제거와 이미 적용한 net 정리
copy_net_nsCLONE_NEWNET이면 새 net을 할당하고 setup_net 호출; 없으면 get_net(old_net)quota/할당/잠금/콜백 실패는 ERR_PTR

구조체와 필드

객체·필드역할과 소유 관계관찰 지점
init_net / struct net정적 기본 객체; 새 net은 net_alloc으로 별도 확보부팅 객체와 런타임 생성 객체를 구별
init_net.gennet_alloc_generic 결과를 RCU 포인터로 연결pernet generic 저장소 기반
pernet_list / pernet_operationssubsystem별 init, pre_exit, exit와 선택적 id/sizesetup_net은 등록 순서로 init, 정리는 역순
pernet_ops_rwsem등록은 쓰기 잠금, 새 net 설정과 cleanup은 읽기 잠금등록·생성·정리 순서 조정
net_namespace_list성공한 net을 RCU 목록에 연결setup 실패 이전에는 공개하지 않음

netns id는 모든 생성의 마지막 고정 단계가 아니다. per-netns ID 매핑은 별도 요구에 따라 할당된다.

실행 단계와 실패 정리

그림 3. setup_net 콜백 경계 (v6.6)
  1. 객체 preinit수명·ID·잠금 등 기반 준비
  2. pernet init 순회ops_init를 목록 순서로 실행
  3. 성공namespace 목록에 공개
  4. 실패성공한 콜백만 역순 정리, 미공개

성공한 ops를 역순으로 pre_exit → synchronize_rcu → exit → free 직접 호출. 등록 실패를 되돌리는 경로와 마지막 참조 뒤 cleanup_net이 실행되는 종료 경로를 구별한다.

경계성공 시실패·종료 시
부팅 net_ns_initinit_net_initialized 후 net_ns_ops 등록초기 설정 불가 시 panic
setup_netnet_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_devicenet_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_initcallnet_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에서 보이는 netdeviceloopback 등록이 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인가?