가시다님의 KANS [3기] 스터디 내용을 정리한 포스트 입니다.
- 컨테이너는 독립된 리눅스 환경(pivot-root, namespace, Overlay filesystem, cgroup)을 보장받는 프로세스이다. - Pivot_root
- 컨테이너는 애플리케이션(프로세스) 동작에 필요한 파일들만 패키징된 이미지를 실행하여 동작합니다.
- 컨테이너는 컨테이너 환경이 조성된곳 어디에서나(온프레미스/클라우드 환경)에서도 실행할 수 있다.
도커(Docker)는 가상실행 환경을 제공해주는 오픈소스 플랫폼입니다. 도커에서는 이 가상실행 환경을 '컨테이너(Container)'라고 부릅니다.
- 좀 더 정확히 표현하는 용어는 '컨테이너화된 프로세스(Containerized Process)' 이다
- 도커 플랫폼이 설치된 곳이라면 컨테이너로 묶인 애플리케이션을 어디서든 실행할 수 있는 장점을 가집니다.

https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/
# 도커 설치
curl -fsSL https://get.docker.com | sh

# 도커 정보 확인 : Client 와 Server , Storage Driver(overlay2), Cgroup Version(2), Default Runtime(runc)
docker info
docker version


# 도커 서비스 상태 확인
systemctl status docker -l --no-pager

# 모든 서비스의 상태 표시 - 링크
systemctl list-units --type=service
# 도커 루트 디렉터리 확인 : Docker Root Dir(/var/lib/docker)
tree -L 3 /var/lib/docker

- The Docker daemon binds to a Unix socket, not a TCP port. By default it's the root user that owns the Unix socket, and other users can only access it using sudo.
- The Docker daemon always runs as the root user
# 도커 서버 정보 획득 실패
docker info
...
Server:
ERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.46/info": dial unix /var/run/docker.sock: connect: permission denied
errors pretty printing info

#
sudo su -
#
sudo systemctl status docker -l --no-pager
docker info

# 소켓 정보 확인 : tcp, udp, sctp, Unix Domain
ss -h | grep sockets
ss -tl # 혹은 ss --tcp --listening
ss -xl # 혹은 ss --unix --listening
ss -xl | grep -i docker
u_str LISTEN 0 4096 /run/docker.sock 69239 * 0
u_str LISTEN 0 4096 /var/run/docker/metrics.sock 69882 * 0
u_str LISTEN 0 4096 /var/run/docker/libnetwork/914c2d2f1446.sock 69422 * 0
# 특정 소켓 파일을 사용하는 프로세스 확인
lsof /run/docker.sock
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root 40u unix 0xffff96bd96236a80 0t0 69239 /run/docker.sock type=STREAM
dockerd 5178 root 4u unix 0xffff96bd96236a80 0t0 69239 /run/docker.sock type=STREAM
# unix domain socket 중 docker 필터링
lsof -U | grep -i docker

# [터미널2] 일반 유저 ubuntu 로 실습 진행
whoami
# Create the docker group : 도커 스크립트 생성 시 자동 생성되어 그룹 확인만 진행
sudo groupadd docker
getent group | tail -n 3
# Add your user to the docker group.
echo $USER
sudo usermod -aG docker $USER
# ssh logout
exit

# ssh 재접속 후 확인
#docker info

컨테이너는 독립된 리눅스 환경(pivot-root, namespace, Overlay filesystem, cgroup)을 보장받는 프로세스

시간 순으로 어떤한 격리 기술이 추가되었는지 알아보겠습니다.
- user 디렉터리를 user 프로세스에게 root 디렉터리로 속임
- 하지만, 탈옥이 가능함
# [터미널1] 관리자 전환
sudo su -
whoami
#
cd /tmp
mkdir myroot
# chroot 사용법 : [옵션] NEWROOT [커맨드]
chroot myroot /bin/sh
chroot: failed to run command ‘/bin/sh’: No such file or directory
#
tree myroot
which sh
ldd /bin/sh
# 바이러리 파일과 라이브러리 파일 복사
mkdir -p myroot/bin
cp /usr/bin/sh myroot/bin/
mkdir -p myroot/{lib64,lib/x86_64-linux-gnu}
tree myroot
cp /lib/x86_64-linux-gnu/libc.so.6 myroot/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 myroot/lib64
tree myroot/
#
w
--------------------
ls
exit
--------------------
#
which ls
ldd /usr/bin/ls
#
cp /usr/bin/ls myroot/bin/
mkdir -p myroot/bin
cp /lib/x86_64-linux-gnu/{libselinux.so.1,libc.so.6,libpcre2-8.so.0} myroot/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 myroot/lib64
tree myroot
#
chroot myroot /bin/sh
--------------------
ls /
## 탈출 가능한지 시도
cd ../../../
ls /
# 아래 터미널2와 비교 후 빠져나오기
exit
--------------------
# chroot 요약 : 경로를 모으고(패키징), 경로에 가둬서 실행(격리)
# [터미널2]
# chroot 실행한 터미널1과 호스트 디렉터리 비교
ls /
[터미널1(위-chroot실행)]와 [터미널2(아래)]의 ls 수행 결과 비교
#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
mkdir(".out", 0755);
chroot(".out");
chdir("../../../../../");
chroot(".");
return execl("/bin/sh", "-i", NULL);
}
# 컴파일
gcc -o myroot/escape_chroot escape_chroot.c
tree -L 1 myroot
file myroot/escape_chroot
# chroot 실행
chroot myroot /bin/sh
-----------------------
ls /
cd ../../
cd ../../
ls /
# 탈출!
./escape_chroot
ls /

# 종료
exit
exit
-----------------------
# [터미널2]
## 루트 디렉터리 비교 및 확인
ls /

마운트 네임스페이스와 Pivot_root를 사용하여 탈옥이 불가능하게 만듭니다.
- chroot 차단을 위해서, pivot_root + mount ns(호스트 영향 격리) 를 사용



주요 명령어
pivot_root
# pivot_root [new-root] [old-root]
## 사용법은 심플합니다 ~ new-root와 old-root 경로를 주면 됩니다
mount
# mount -t [filesystem type] [device_name] [directory - mount point]
## root filesystem tree에 다른 파일시스템을 붙이는 명령
## -t : filesystem type ex) -t tmpfs (temporary filesystem : 임시로 메모리에 생성됨)
## -o : 옵션 ex) -o size=1m (용량 지정 등 …)
## 참고) * /proc/filesystems 에서 지원하는 filesystem type 조회 가능
unshare
# unshare [options] [program] [arguments]]
## "새로운 네임스페이스를 만들고 나서 프로그램을 실행" 하는 명령어입니다
# [터미널1]
unshare --mount /bin/sh
-----------------------
# 아래 터미널2 호스트 df -h 비교 : mount unshare 시 부모 프로세스의 마운트 정보를 복사해서 자식 네임스페이스를 생성하여 처음은 동일
df -h
-----------------------
# [터미널2]
df -h
양쪽이 동일함
# [터미널1]
-----------------------
#
mkdir new_root
mount -t tmpfs none new_root
ls -l
tree new_root
## 마운트 정보 비교 : 마운트 네임스페이스를 unshare
df -h
mount | grep new_root
findmnt -A

## 파일 복사 후 터미널2 호스트와 비교
cp -r myroot/* new_root/
tree new_root/
-----------------------
# [터미널2]
cd /tmp
ls -l
tree new_root
df -h
mount | grep new_root
findmnt -A
## 안보이는 이유 : 마운트 네임스페이스를 unshare 된 상태
tree new_root/
[터미널1(위-마운트 네임스페이스 적용)]와 [터미널2(아래)]의 수행 결과 비교
- [터미널2]에서는 new_root 가 보이지 않음


# 터미널1
-----------------------
mkdir new_root/put_old
## pivot_root 실행
cd new_root # pivot_root 는 실행 시, 변경될 root 파일시스템 경로로 진입
pivot_root . put_old # [신규 루트] [기존 루트]
##
cd /
ls / # 터미널2와 비교
ls put_old
-----------------------
# 터미널2
ls /

탈옥 시도
# 터미널1
-----------------------
./escape_chroot
cd ../../../
ls /
exit
exit
-----------------------

네임스페이스와 관련된 프로세스의 특징
- 모든 프로세스들은 네임스페이스 타입별로 특정 네임스페이스에 속합니다
- Child 는 Parent 의 네임스페이스를 상속받습니다
- 프로세스는 네임스페이스 타입별로 일부는 호스트(root) 네임스페이스를 사용하고 일부는 컨테이너의 네임스페이스를 사용할 수 있습니다.



- 네임스페이스와 관련된 프로세스의 특징
- 모든 프로세스들은 네임스페이스 타입별로 특정 네임스페이스에 속합니다
- Child 는 Parent 의 네임스페이스를 상속받습니다
- 프로세스는 네임스페이스 타입별로 일부는 호스트(root) 네임스페이스를 사용하고 일부는 컨테이너의 네임스페이스를 사용할 수 있습니다.
- mount 네임스페이스는 컨테이너의 것으로 격리하고, network 네임스페이스는 호스트 것을 사용
- Mount(파일시스템), Network(네트워크), PID(프로세스 id), User(계정), ipc(프로세스간 통신), Uts(Unix time sharing, 호스트네임), cgroup


# [터미널 1,2] 관리자
sudo su -
cd /tmp
# 네임스페이스 확인 방법 1 : 프로세스 별 네임스페이스 확인
ls -al /proc/$$/ns

## 특정 네임스페이스의 inode 값만 확인
readlink /proc/$$/ns/mnt
readlink /proc/$$/ns/net
# 네임스페이스 확인 방법 2 : lsns - List system namespaces
lsns -h
lsns -p 1
lsns -p $$
## -t 네임스페이스 타입 , -p 조회할 PID
## NPROCS : 해당 네임스페이스에 속해있는 프로세스 갯수
## PID : 해당 네임스페이스의 (최초) 주인 프로세스
lsns -t mnt -p 1
lsns -t mnt -p $$
# PID 1과 현재 Shell 속한 프로세스의 MNT NS 정보 확인
lsns -t mnt -p 1
lsns -t mnt -p $$
# [터미널1] /tmp 디렉터리
# unshare -m [명령어] : -m 옵션을 주면 [명령어]를 mount namespace 를 isolation 하여 실행합니다
unshare -m # *[명령어]를 지정하지 않으면 환경변수 $SHELL 실행
-----------------------------------
# NPROCS 값과 PID 값의 의미 확인
lsns -p $$
# PID 1과 비교
lsns -p 1
# 빠져나오기
exit
-----------------------------------

# unshare -u [명령어]
# -u 옵션을 주면 [명령어]를 UTS namespace 를 isolation 하여 실행
# [터미널1] /tmp 디렉터리
unshare -u
-----------------------------------
lsns -p $$
lsns -p 1
## 기본은 부모 네임스페스의 호스트 네임을 상속
hostname
## 호스트 네임 변경
hostname KANS
## 아래 터미널2에서 hostname 비교
hostname
exit
-----------------------------------
# [터미널2] /tmp 디렉터리
hostname

# [터미널1]
ipcs -m
docker run --rm --name test1 --ipc=shareable -it ubuntu bash
---------------------
ipcs -m
ipcmk -M 2000
Shared memory id: 0
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x6427dde0 0 root 644 2000 0
lsns -p $$
# 아래 실습 확인 후 종료
exit
---------------------

# [터미널2]
# 호스트에서 확인
ipcs -m
# 컨테이너 생성 시 IPC 공유 : 해당 컨테이너는 test1 컨테이너와 IPC 네임스페이스를 공유
docker run --rm --name test2 --ipc=container:test1 -it ubuntu bash
---------------------
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x6427dde0 0 root 644 2000 0
lsns -p $$
# 실습 확인 후 종료
exit
---------------------

- 부모-자식 네임스페이스 중첩 구조, 부모 네임스페이스 에서는 → 자식 네임스페이스를 볼 수 있음
- 자식 네임스페이스는 parent tree 의 id 와 subtree 의 id 두 개를 가짐
# unshare -p [명령어]
## -p 옵션을 주면 [명령어]를 PID namespace 를 isolation 하여 실행합니다
## -f(fork) : PID namespace 는 child 를 fork 하여 새로운 네임스페이스로 격리함
## --mount-proc : namespace 안에서 ps 명령어를 사용하려면 /proc 를 mount 하기위함
# [터미널1] /proc 파일시스템 마운트
echo $$
unshare -fp --mount-proc /bin/sh
--------------------------------
# 터미널2 호스트와 비교
echo $$
ps -ef
ps aux
# 내부에서 PID NS 확인 : 아래 터미널2에서 lsns -t pid -p <위 출력된 PID>와 비교
lsns -t pid -p 1
--------------------------------
# [터미널2]
ps -ef
ps aux
ps aux | grep '/bin/sh'
root 6186 0.0 0.0 6192 1792 pts/2 S 15:08 0:00 unshare -fp --mount-proc /bin/sh
root 6187 0.0 0.0 2892 1664 pts/2 S+ 15:08 0:00 /bin/sh
# 터미널1 PID NS와 비교
lsns -t pid -p <위 출력된 PID>
lsns -t pid -p 6187

# [터미널1]
--------------------------------
# fork
sleep 10000
# 아래 종료로 자동으로 sleep 가 exit 됨
echo $$
# 아래 종료로 자동으로 exit됨 : 컨테이너의 PID 1 프로세스 종료 시
--------------------------------
echo $$
# [터미널2]
ps aux | grep sleep
## 호스트에서 sleep 종료 시켜보기 : 어떻게 되는가?
kill -l
kill -SIGKILL $(pgrep sleep)
## 호스트에서 /bin/sh 종료 시켜보기 : 어떻게 되는가?
ps aux | grep '/bin/sh'
kill -SIGKILL <위 출력된 PID>
kill -9 6187


# 터미널1,2
exit
whoami
# 터미널1
docker run -it ubuntu /bin/sh
-----------------------------
# 아래 터미널2와 비교
whoami
id
# 아래 터미널2와 비교
ps -ef
# User 네임스페이스는 도커 컨테이너 실행 시, 호스트 User 를 그대로 사용
readlink /proc/$$/ns/user
lsns -p $$

# 아래 동작 확인 후 종료
exit
-----------------------------
# 터미널2
whoami
id
## root 로 실행됨
ps -ef |grep "/bin/sh"
ubuntu 6733 5348 0 15:34 pts/0 00:00:00 docker run -it ubuntu /bin/sh
root 6790 6768 0 15:34 pts/0 00:00:00 /bin/sh
##
readlink /proc/$$/ns/user
lsns -p $$
lsns -p $$ -t user
NS TYPE NPROCS PID USER COMMAND
4026531837 user 5 2391 ubuntu /lib/systemd/systemd --user

컨테이너를 탈취 후, 해당 프로세스를 기반으로 호스트에 Action 이 가능할 경우, root 계정 권한 실행이 가능 ⇒ 보안상 취약
# 터미널1
unshare -U --map-root-user /bin/sh
-----------------------------
# 내부에서는 여전히 root로 보임
whoami
id
# User 네임스페이스를 호스터(터미널2)와 비교
readlink /proc/$$/ns/user
lsns -p $$
# 아래 동작 확인 후 종료
exit
-----------------------------
# 터미널2
readlink /proc/$$/ns/user
lsns -p $$
## ubuntu 로 실행됨
ps -ef |grep "/bin/sh"
ubuntu 6874 5348 0 15:42 pts/0 00:00:00 /bin/sh





- 프로세스는 실행 중인 프로그램의 인스턴스를 의미. OS에서 프로세스를 관리하며, 각 프로세스는 고유한 ID(PID)를 가짐.
- 프로세스는 CPU와 메모리를 사용하는 기본 단위로, OS 커널(cgroup)에서 각 프로세스의 자원을 관리함.
- cgroups : Blog1 , Blog2
- cgroup v1, v2 : Blog1 , Blog2 , check
- cgroup v1, v2가 존재하며 v2는 v1에 비해 자원계층구조의 가시성을 향상.
- cgroup v1에서는 request, limit 두개의 자원설정만이 가능했는데, cgroup v2에서는 memoryQoS라는 기능을 추가하여 컨테이너 등에서 쉽사리 OOM이 나지않게 하는 기능 지원 → Memory High : 메모리 사용량 조절 제한. cgroup의 사용량이 높은 경계를 초과하면 cgroup의 프로세스가 제한되고 회수 압력이 커짐
- control groups : 프로세스들의 자원의 사용(CPU, 메모리, 디스크 입출력, 네트워크 등)을 제한, 격리시키는 리눅스 커널 기능
- 하나 또는 복수의 장치를 묶어서 하나의 그룹을 만들 수 있으며 개별 그룹은 시스템에서 설정한 값만큼 하드웨어를 사용
- 시스템의 프로세스들은 장치별로 특정한 cgroup에 속하여 프로세스가 사용하는 하드웨어 자원의 총량에 제한을 받음
- process를 계층적인 group으로 구성해서, resource 사용을 제한하고 모니터링할 수 있는 linux kernel feature
- cgroup의 interface는 cgroupfs이라 불리는 pseudo-filesystem을 통해 제공됨
- cgroupfs의 subdirectory를 생성/삭제/변경하면서 정의됨
- /proc 와 /sys
- 커널 2.x 버전까지는 /proc 폴더 하나만 있었지만, 3.x 버전 부터 /sys를 추가로 구분하였음
- Runtime의 메모리 정보를 파일시스템 폴더에 mount한 pseudo-filesystem
- /proc, /sys 는 커널이 관리하는 메모리를 마운트했기 때문에 해당 디렉토리에 접근하는 순간 메모리에서 정보를 읽어 오게 됨
#
mount -t cgroup
mount -t cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
#
findmnt -t cgroup2
TARGET SOURCE FSTYPE OPTIONS
/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot
# cgroup2 이외에 proc, bpf 도 있음
findmnt -A
TARGET SOURCE FSTYPE OPTIONS
/ /dev/nvme0n1p1 ext4 rw,relatime,discard,errors=remount-ro
...
├─/proc proc proc rw,nosuid,nodev,noexec,relatime
...
├─/sys sysfs sysfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/kernel/security securityfs securityfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot
│ ├─/sys/fs/pstore pstore pstore rw,nosuid,nodev,noexec,relatime
│ ├─/sys/firmware/efi/efivars efivarfs efivarfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/bpf bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700
...

# cgroupv1 만 지원 시, cgroup2 출력되지 않음
grep cgroup /proc/filesystems
nodev cgroup
nodev cgroup2
stat -fc %T /sys/fs/cgroup/
cgroup2fs
# 터미널2
sleep 100000
# /proc에 cgroup 정보 확인
cat /proc/cgroups
cat /proc/$(pgrep sleep)/cgroup
0::/user.slice/user-1000.slice/session-7.scope
tree /proc/$(pgrep sleep) -L 2
...
├── ns
│ ├── cgroup -> cgroup:[4026531835]
│ ├── ipc -> ipc:[4026531839]
│ ├── mnt -> mnt:[4026531841]
│ ├── net -> net:[4026531840]
...
# cgroup 목록 확인
ls /sys/fs/cgroup
cat /sys/fs/cgroup/cgroup.controllers
#
tree /sys/fs/cgroup/ -L 1
tree /sys/fs/cgroup/ -L 2
tree /sys/fs/cgroup/user.slice -L 1
tree /sys/fs/cgroup/user.slice/user-1000.slice -L 1
tree /sys/fs/cgroup/user.slice/user-1000.slice -L 2
# 터미널1,2 관리자로 실습 진행
sudo su -
whoami
# 툴 설치
apt install cgroup-tools stress -y
# 터미널2 : 아래 stress 실행 후 CPU 사용률 확인
htop

# 터미널1에서 실습 진행
# 먼저 부하 발생 확인
stress -c 1
# 디렉터리 이동
cd /sys/fs/cgroup
# 서브 디렉터리 생성 후 확인 확인
mkdir test_cgroup_parent && cd test_cgroup_parent
tree
# 제어 가능 항목 확인
cat cgroup.controllers
# cpu를 subtree이 추가하여 컨트롤 할 수 있도록 설정 : +/-(추가/삭제)
cat cgroup.subtree_control
echo "+cpu" >> /sys/fs/cgroup/test_cgroup_parent/cgroup.subtree_control
# cpu.max 제한 설정 : 첫 번쨰 값은 허용된 시간(마이크로초) 두 번째 값은 총 기간 길이 > 1/10 실행 설정
echo 100000 1000000 > /sys/fs/cgroup/test_cgroup_parent/cpu.max
# test용 자식 디렉토리를 생성하고, pid를 추가하여 제한을 걸어
mkdir test_cgroup_child && cd test_cgroup_child
echo $$ > /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs
cat /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs
cat /proc/$$/cgroup
# 부하 발생 확인 : 터미널2에 htop 확인
stress -c 1

# 값 수정
echo 1000000 1000000 > /sys/fs/cgroup/test_cgroup_parent/cpu.max
# 부하 발생 확인 : 터미널2에 htop 확인
stress -c 1

# cgroup 삭제
exit
sudo su -
rmdir /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child
rmdir /sys/fs/cgroup/test_cgroup_parent
# 아래는 cgroup v1 경우
---------------------------
## 1. 제어그룹 생성 : mycgroup
### -a : owner 설정 (control group's file)
### -g : cgroup 설정 <controllers>:<path>
### -g cpu:mycgroup ~ cpu controller 를 사용하고 path 는 mycgroup
cgcreate -a root -g cpu:mycgroup
tree /sys/fs/cgroup/cpu/mycgroup
## 2. 제어그룹 리소스 설정 : CPU 사용률 설정
### cpu 사용률(%CPU)
### cpu.cfs_quota_us / cat cpu.cfs_period_us * 100
### 참고 1000us = 1ms
### cpu사용률(30%)을 설정 (30,000/100,000)x100=30%
cgset -r cpu.cfs_quota_us=30000 mycgroup
cat /sys/fs/cgroup/cpu/mycgroup/cpu.cfs_quota_us
## 3. 제어그룹 프로세스 할당 : stress 실행
cgexec -g cpu:mycgroup stress -c 1

https://malwareanalysis.tistory.com/249

# 터미널1~3 관리자
sudo su -
whoami
# veth (가상 이더넷 디바이스) 생성, man ip-link
ip link add veth0 type veth peer name veth1
# veth 생성 확인(상태 DOWN), ifconfig 에는 peer 정보 확인 안됨
# very pair 정보 확인 : ({iface}@if{pair#N})
ip -c link
ip -c addr # 축약 ip -c a
ifconfig -a
# 네트워크 네임스페이스 생성 , man ip-netns
ip netns add RED
ip netns add BLUE
# 네트워크 네임스페이스 확인
ip netns list
# veth0 을 RED 네트워크 네임스페이스로 옮김
ip link set veth0 netns RED
ip netns list
## 호스트의 ip a 목록에서 보이지 않음, veth1의 peer 정보가 변경됨
ip -c link
## RED 네임스페이스에서 ip a 확인됨(상태 DOWN), peer 정보 확인, link-netns RED, man ip-netns
ip netns exec RED ip -c a
# veth1 을 BLUE 네트워크 네임스페이스로 옮김
ip link set veth1 netns BLUE
ip -c link
ip netns exec BLUE ip -c a
# veth0, veth1 상태 활성화(state UP)
ip netns exec RED ip link set veth0 up
ip netns exec RED ip -c a
ip netns exec BLUE ip link set veth1 up
ip netns exec BLUE ip -c a
# veth0, veth1 에 IP 설정
ip netns exec RED ip addr add 11.11.11.2/24 dev veth0
ip netns exec RED ip -c a
ip netns exec BLUE ip addr add 11.11.11.3/24 dev veth1
ip netns exec BLUE ip -c a
# 터미널1 (RED 11.11.11.2)
## nsenter : 네임스페이스에 attach 하여 지정한 프로그램을 실행
tree /var/run/netns
nsenter --net=/var/run/netns/RED
ip -c a
## neighbour/arp tables management , man ip-neighbour
ip -c neigh
## 라우팅 정보, iptables 정보
ip -c route
iptables -t filter -S
iptables -t nat -S
# 터미널2 (호스트)
lsns -t net # nsenter 실행 후 TYPE(net) CMD(-bash) 생성 확인
ip -c a
ip -c neigh
ip -c route
iptables -t filter -S
iptables -t nat -S
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip -c a
ip -c neigh
ip -c route
iptables -t filter -S
iptables -t nat -S
# ping 통신 확인
# 터미널3 (BLUE 11.11.11.3)
tcpdump -i veth1
ip -c neigh
exit
# 터미널1 (RED 11.11.11.2)
ping 11.11.11.3 -c 1
ip -c neigh
exit
# 삭제
ip netns delete RED
ip netns delete BLUE

arp table, route table, iptables 와 호스트의 bridge fdb 를 통하여 통신

# 네트워크 네임스페이스 및 veth 생성
ip netns add RED
ip link add reth0 type veth peer name reth1
ip link set reth0 netns RED
ip netns add BLUE
ip link add beth0 type veth peer name beth1
ip link set beth0 netns BLUE
# 확인
ip netns list
ip -c link
ip netns exec RED ip -c a
ip netns exec BLUE ip -c a
# 브리지 정보 확인
brctl show
# br0 브리지 생성
ip link add br0 type bridge
# br0 브리지 정보 확인
brctl show br0
brctl showmacs br0
brctl showstp br0
# reth1 beth1 을 br0 연결
ip link set reth1 master br0
ip link set beth1 master br0
brctl show br0
brctl showmacs br0
ip -br -c link
# reth0 beth0 에 IP 설정 및 활성화, br0 활성화
ip netns exec RED ip addr add 11.11.11.2/24 dev reth0
ip netns exec BLUE ip addr add 11.11.11.3/24 dev beth0
ip netns exec RED ip link set reth0 up; ip link set reth1 up
ip netns exec BLUE ip link set beth0 up; ip link set beth1 up
ip link set br0 up
ip -br -c addr
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
ip -c a;echo; ip -c route;echo; ip -c neigh
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
RED
# 터미널2 (호스트)
brctl showmacs br0
bridge fdb show
bridge fdb show dev br0
iptables -t filter -S
iptables -t filter -L -n -v
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip -c a;echo; ip -c route;echo; ip -c neigh
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
BLUE
==============================================================
# 터미널2 (호스트)
# ping 통신 전 사전 설정
## iptables 정보 확인
iptables -t filter -S | grep '\-P'
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
iptables -nvL -t filter
## Ubuntu 호스트에서 패킷 라우팅 설정 확인 : 커널의 IP Forwarding (routing) 기능 확인 - 0(off), 1(on)
## echo 1 > /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/ip_forward
1
==============================================================
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2) >> ping 왜 실패했을까요?
ping -c 1 11.11.11.3
# 터미널2 (호스트)
tcpdump -l -i br0
watch -d 'iptables -v --numeric --table filter --list FORWARD'
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
# 터미널3 (BLUE 11.11.11.3)
tcpdump -l -i beth0

RED → BLUE ping failed 분석

RED → BLUE ping 허용 설정
호스트 입장에서는 "외부(RED, src) → 외부(BLUE, dst)" 패킷이므로 FORWARD 체인의 filter 테이블 룰을 봐야합니다 → 허용 정책 추가하자
# 터미널2 (호스트)
# iptables 설정 정보 확인
iptables -t filter -S
iptables -t nat -S | grep '\-P'
# iptables 설정 추가 -t(table), -I(insert chain), -j(jump to - ACCEPT 허용)
iptables -t filter -I DOCKER-USER -j ACCEPT
iptables -nvL -t filter
iptables -t filter -S
iptables -t nat -S | grep '\-P'
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
tcpdump -l -i br0
==============================================================
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2)
ping -c 1 11.11.11.3
ip -c neigh
# 터미널2 (호스트)
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
tcpdump -l -i br0
## 정보 확인
ip -c neigh
# 터미널3 (BLUE 11.11.11.3)
tcpdump -l -i beth0
ip -c neigh

==============================================================
# (옵션) 추가 방안1 : 출발지 IP 11.2, 11.3 허용
## -t(table), -A(APPEND chain rule), -s(출발지), -j(jump to - ACCEPT 허용)
iptables -t filter -A FORWARD -s 11.11.11.2/32 -j ACCEPT
iptables -t filter -A FORWARD -s 11.11.11.3/32 -j ACCEPT
## 추가 정책 삭제 시
iptables -t filter -D FORWARD -s 11.11.11.2/32 -j ACCEPT
iptables -t filter -D FORWARD -s 11.11.11.3/32 -j ACCEPT
# (옵션) 추가 방안2 : 11.11.11.0/24 대역 출발지 허용
iptables -t filter -A FORWARD -s 11.11.11.0/24 -j ACCEPT
## 추가 정책 삭제 시
iptables -t filter -D FORWARD -s 11.11.11.0/24 -j ACCEPT
# (옵션) 추가 방안3 : FORWARD 기본 정책 허용으로 변경
iptables -t filter -P FORWARD ACCEPT
## 추가 정책 삭제 시
iptables -t filter -P FORWARD DROP
호스트에 RED/BLUE와 통신 가능한 IP 설정 및 라우팅 추가, iptables NAT 를 통하여 통신

호스트에서 RED 나 BLUE 로 ping 통신 → RED 에서 외부로 통신

# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
tcpdump -i any
# 터미널3 (호스트)
exit
tcpdump -i br0 -n
# 터미널2 (호스트) >> 호스트에서 RED 로 통신이 안되는 이유가 무엇일까요?
ping -c 1 11.11.11.2
ip -c route
ip -c addr

==============================================================
# 터미널2 (호스트) >> br0 에 IP 추가(라우팅 정보)
ip addr add 11.11.11.1/24 dev br0
ping -c 1 11.11.11.2
ping -c 1 11.11.11.3

# 터미널1 (RED 11.11.11.2) >> 192.168.50.10 와 통신이 안되는 이유는 무엇일까요?
ping -c 1 11.11.11.1
ping -c 1 192.168.50.10
ip -c route
ip -c addr

==============================================================
# 터미널3 (호스트)
tcpdump -i any icmp -n
# 터미널1 (RED 11.11.11.2)
ip route add default via 11.11.11.1
ping -c 1 192.168.50.10
ip -c route
ping -c 1 8.8.8.8 >> 외부 대역(인터넷)과 통신이 안되는 이유가 무엇일까요?

# 터미널2 (호스트)
iptables -S -t nat
iptables -nvL -t nat
## POSTROUTING : 라우팅 Outbound or 포워딩 트래픽에 의해 트리거되는 netfilter hook
## POSTROUTING 에서는 SNAT(Source NAT) 설정
iptables -t nat -A POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
watch -d 'iptables -v --numeric --table nat --list POSTROUTING'
iptables -nvL -t nat
conntrack -L --src-nat
# 터미널1 (RED 11.11.11.2)
ping -c 1 8.8.8.8
exit
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE # 주의, 꼭 실행 후 아래 진행 할 것
ip route add default via 11.11.11.1
ping -c 1 8.8.8.8
exit
# 삭제
ip netns delete RED
ip netns delete BLUE
ip link delete br0
iptables -t filter -D DOCKER-USER -j ACCEPT
iptables -t nat -D POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
SNAT 추가한 이후 ping이 잘 되는것을 확인할 수 있었습니다.