Hamutaro

안녕하세요.

오늘은 비가 정말 많이 오네요,, 지각할 뻔했어요,,

그럼 모닝30 시작!

 

1. 왜 이걸 배우는지 찾아봅니다

1.1 보안 담당자는 서버에서 왜 grep 을 쓸까요?

> 로그 파일에서 특정 문자열이나 이상 징후를 빠르게 찾아 보안 사고를 탐지하고 분석하기 위해서이다.

grep은 리눅스에서 파일이나 명령어 출력 내용 중 특정 문자열 또는 패턴이 포함된 줄을 검색하는 명령어입니다.

grep [옵션] "검색할 문자열" 파일명

자주 사용하는 로그 확인 명령어

# 로그 전체 출력
cat /var/log/syslog

# 로그 마지막 20줄 확인
tail -n 20 /var/log/syslog

# 로그 실시간 확인
tail -f /var/log/syslog

# 특정 문자열 검색
grep "error" /var/log/syslog

# systemd 서비스 로그 확인
journalctl -u ssh

# 최근 로그 확인
journalctl -n 50

2. grep 과 옵션의 뜻을 알아봅니다

옵션설명사용 예시

-i 대소문자 구분 없이 검색 grep -i "error" server.log
-n 검색 결과의 줄 번호 표시 grep -n "ERROR" server.log
-v 검색 문자열이 없는 줄 출력 grep -v "INFO" server.log
-c 일치하는 줄의 개수 출력 grep -c "ERROR" server.log
-r 하위 디렉터리까지 재귀 검색 grep -r "ERROR" /var/log/
-E 확장 정규표현식 사용 grep -E "ERROR\|WARN" server.log

참고로 -c는 문자열의 총 등장 횟수가 아니라 일치하는 줄 수를 계산합니다.

 

 


 

오늘의 순서

교시무엇
2교시 IP 주소 · 사설 주소
3교시 서브넷
4교시 서브네팅
5교시 스위치와 라우터
6교시 VLAN
7교시 라우팅 표

 

2교시 — IP 주소를 펼치기 · 사설 IP와 공인 IP

왜 필요한가

보안 관제에서 보는 로그(방화벽 · 웹 서버 · 침입 탐지 시스템 로그)에는 거의 언제나 출발지 IP 주소와 목적지 IP 주소 필드가 기록됩니다. 분석은 그 주소가 내부망(우리 회사 네트워크)인지 외부망(인터넷)인지 가리는 것부터 시작합니다.

그 기준이 사설 IP(회사 · 가정의 내부망에서만 쓰는 주소)와 공인 IP(인터넷 전체에서 하나뿐인 주소)입니다.

내부망 PC의 사설 IP는 인터넷과 통신할 때 공유기 · 라우터의 NAT(냇 · Network Address Translation, 주소 변환)를 거쳐 공인 IP로 바뀌어 나갑니다. 그래서 외부 서버나 방화벽 로그에 기록된 출발지 주소는 실제 PC의 사설 IP가 아니라 공유기의 공인 IP일 수 있습니다. 어느 PC가 보낸 것인지 찾으려면 공유기 · 방화벽에 남은 NAT 변환 기록을 함께 봐야 합니다.


2.1 IP 주소는 32비트

비트 (bit)

0 또는 1 하나를 담는 2진수 한 자리. 컴퓨터가 다루는 가장 작은 정보 단위입니다.

IPv4 주소 하나는 32비트로 되어 있습니다.

점으로 나뉜 마디(옥텟 · octet) 하나는 8비트라, 마디마다 0~255를 담습니다.

2진수 (이진수)

0과 1 두 글자로만 쓰는 수. 자리 하나가 왼쪽으로 갈수록 2배씩 커집니다(1 · 2 · 4 · 8 · …).

예: 192는 128 + 64라서 11000000

컴퓨터는 IP 주소를 이 모양으로 읽고, 서브넷 마스크도 이 모양으로 비교합니다.

192.168.10.25는 사람이 읽는 모양입니다. 컴퓨터는 점으로 나뉜 마디 네 개를 각각 8비트 2진수로 읽습니다. 그래서 IPv4(아이피 브이포) 주소는 모두 32비트입니다.

마디10진수(평소 쓰는 수)2진수(0과 1로만 쓰는 수 · 8비트)
1 192 11000000
2 168 10101000
3 10 00001010
4 25 00011001

8비트가 전부 1이면 11111111 = 255입니다. 그래서 한 마디에는 0~255만 들어갑니다(256가지).

🐍 문법 상자 · .split(".") · 리스트[번호] — 주소를 마디로 나누기

.split(".")은 글자를 점에서 나눠 리스트로 리턴합니다. 리스트의 값 하나는 리스트[번호]로 꺼내고, 번호는 0부터 셉니다.

parts = "192.168.10.25".split(".")  # .split(".") 은 글자를 점에서 나눈 리스트를 리턴한다. 그 리스트를 parts 에 담는다print(parts)                        # 리스트 parts 전체를 출력한다. 마디 네 개가 따옴표 붙은 글자로 들어 있다print(parts[0])                     # parts[0] 은 리스트의 첫 번째 값이다. 첫째 마디 글자를 출력한다print(int(parts[3]) + 1)            # 넷째 마디 글자를 int() 로 숫자로 바꾸고 1 을 더해 출력한다
['192', '168', '10', '25']
192
26
쓰는 것뜻
글자.split(".") 글자를 점에서 나눈 리스트를 리턴한다
리스트[0] 리스트의 첫 번째 값. 번호는 0부터 센다
int("25") 글자 "25"를 숫자 25로 바꿔 리턴한다. 글자인 채로는 더하기 계산을 할 수 없다

🐍 문법 상자 · format(숫자, "08b") — 8비트 2진수로

print(format(5, "08b"))        # format(숫자, "08b") 는 숫자를 앞자리를 0 으로 채운 8자리 2진수 글자로 바꾼다. 5 를 바꿔 출력한다print(format(255, "08b"))      # 255 를 8자리 2진수 글자로 바꿔 출력한다. 255 는 8비트로 나타낼 수 있는 가장 큰 수다bits = format(25, "08b")       # 25 를 8자리 2진수 글자로 바꿔 변수 bits 에 담는다print(len(bits))               # len() 은 글자 수를 리턴한다. bits 의 글자 수를 출력한다
00000101
11111111
8
쓰는 것뜻
b 2진수(binary)로 바꾼다
08 빈 앞자리를 0으로 채워 8자리를 만든다
int("25") 글자 "25"를 숫자 25로

⚠ "08b"를 따옴표 없이 08b로 쓰면 SyntaxError입니다. 글자로 넘겨야 합니다.


2.2 사설 IP와 공인 IP

사설 IP (private IP)

회사 · 가정의 내부망에서만 쓰는 주소. 이 주소 그대로는 인터넷에서 통신하지 못합니다.

내부망에서만 쓰므로 다른 회사와 같은 주소를 써도 충돌하지 않습니다.

정해진 세 범위:

  • 10.x.x.x
  • 172.16~31.x.x
  • 192.168.x.x

관제에서는 로그의 주소가 이 범위면 「내부망 장비」로 먼저 분류합니다.

공인 IP (public IP)

인터넷 전체에서 하나뿐인 주소. 통신사가 나눠 줍니다.

인터넷의 서버들은 이 주소로 서로를 찾습니다.

예: 8.8.8.8(구글 DNS 서버)

사설 IP 범위예주의
10.0.0.0 ~ 10.255.255.255 10.0.0.8 첫 마디가 10이면 전부
172.16.0.0 ~ 172.31.255.255 172.20.5.6 ⚠ 둘째 마디가 16~31일 때만. 172.32.5.6은 공인
192.168.0.0 ~ 192.168.255.255 192.168.10.25 첫 두 마디가 192.168

세 범위는 인터넷 공통 약속입니다. 세 범위에 들지 않는 주소는 공인 IP입니다.

사설 IP는 회사마다 겹쳐도 됩니다. 인터넷으로는 나가지 않기 때문입니다.

사설이면 안전, 공인이면 공격이라는 뜻은 아닙니다. 내부망과 외부망을 가르는 첫 기준일 뿐입니다.


2.3 NAT — 사설 IP를 공인 IP로 변환

NAT (Network Address Translation)

내부망의 사설 IP를 인터넷으로 보낼 때 공인 IP로 바꾸는 주소 변환.

쓰는 이유 ① 사설 IP는 인터넷에서 쓸 수 없는 주소라, 그대로 보내면 응답이 돌아올 수 없습니다.

쓰는 이유 ② IPv4 주소는 약 43억 개뿐이라 모든 PC에 공인 IP를 줄 수 없습니다. 공인 IP 하나를 내부망 PC 여러 대가 함께 씁니다.

집 · 학원의 공유기가 이 일을 합니다.

내부망 PC가 보낸 패킷의 출발지 주소를 자기 공인 IP로 바꿔 보내고, 응답이 오면 변환 기록을 보고 원래 PC의 사설 IP로 되돌려 전달합니다.

그래서 외부 서버의 로그에는 내 PC의 사설 IP가 아니라 공유기의 공인 IP가 남습니다.

PAT (Port Address Translation)

공인 IP 하나를 내부망의 여러 PC가 함께 쓰는 NAT.

연결마다 공인 IP 쪽 포트 번호를 다르게 붙이고, 응답이 오면 그 포트 번호로 어느 PC의 연결인지 찾아 전달합니다.

집 · 학원 공유기는 대부분 이 방식입니다.

공유기는 내부망 PC가 인터넷과 통신할 때 출발지 주소를 자기 공인 IP로 바꿔 보냅니다. 이것이 NAT입니다.

방식바꾸는 법
정적 NAT [찾아 쓰기] 2-2 (다-2) · (다-4)에서 검색해 보고서에 씁니다
동적 NAT [찾아 쓰기] 2-2 (다-2) · (다-3)에서 검색해 보고서에 씁니다
PAT 공인 IP 하나를 여러 PC가 같이 쓰고, 포트 번호로 누구 것인지 구별한다 — 집 · 학원 공유기가 대부분 이것

PAT 공유기는 「공인 IP 쪽 포트 40002번 = 내부망 192.168.10.25:51234」 같은 변환표를 가지고 있다가, 답이 오면 표를 보고 원래 PC로 넘겨 줍니다.


 

3교시 — 서브넷 마스크 · CIDR · 같은 망 판정

왜 필요한가

주소 두 개가 같은 망인지 알아야 통신이 어떤 장비를 거쳐 가는지 말할 수 있습니다.

같은 망이면 스위치(같은 망 안의 장비들을 잇는 장비)가 바로 전달하고, 다른 망이면 게이트웨이(라우터 · 망과 망을 잇는 장비)를 거칩니다.

방화벽(허용 · 차단 규칙으로 통신을 거르는 장비) 규칙도 이 경계로 씁니다.

그 경계를 정하는 값이 255.255.255.0 같은 서브넷 마스크입니다.


3.1 서브넷 마스크 — 마디로 먼저 읽기

서브넷 (subnet)

큰 망을 나눈 작은 망 하나.

같은 서브넷 안의 장비끼리는 공유기 · 라우터를 거치지 않고 바로 통신합니다.

회사는 부서마다 서브넷을 나눠, 부서끼리 오가는 통신을 따로 관리합니다.

서브넷 마스크 (subnet mask)

IP 주소에서 어디까지가 같은 망인지 정하는 값.

255인 마디는 망 부분, 0인 마디는 장비 부분입니다.

망 부분은 이 장비가 어느 망에 속하는지를 나타내는 앞쪽 자리입니다. 같은 망에 있는 장비들은 이 부분이 모두 똑같습니다.

장비 부분은 그 망 안에서 어느 장비인지를 나타내는 뒤쪽 자리입니다. 장비마다 다릅니다.

예: 255.255.255.0이면 앞 세 마디가 망 부분.

예: 192.168.10.1 · 192.168.10.25 · 192.168.10.30은 망 부분 192.168.10이 같아서 같은 망입니다. 장비 부분 1 · 25 · 30으로 장비를 구별합니다.

컴퓨터는 IP 주소와 서브넷 마스크를 2진수로 바꿔 AND 연산을 합니다. 그 결과가 그 주소의 네트워크 주소입니다.

두 주소의 네트워크 주소가 같으면 같은 망입니다.

CIDR (Classless Inter-Domain Routing)

서브넷 마스크를 /숫자로 짧게 쓰는 표기.

/ 뒤의 숫자는 서브넷 마스크를 2진수로 바꿨을 때 1의 개수입니다.

255를 2진수로 바꾸면 11111111로 1이 8개입니다. 그래서 255인 마디 하나는 8로 셉니다.

0을 2진수로 바꾸면 00000000으로 1이 0개입니다.

예:

255.255.255.0
11111111.11111111.11111111.00000000

1이 8개인 마디가 세 개라 8 × 3 = 24, 그래서 /24입니다.

255.255.0.0
11111111.11111111.00000000.00000000

1이 8개인 마디가 두 개라 8 × 2 = 16, 그래서 /16입니다.

호스트 (host)

망 안에서 주소를 받는 장비 하나.

예: PC · 서버 · 프린터 · 스마트폰.

마디는 IP 주소에서 점으로 나뉜 숫자 하나입니다.

첫째 마디둘째 마디셋째 마디넷째 마디
192 168 10 25

서브넷 마스크는 「1인 자리는 망 부분, 0인 자리는 장비 부분」이라고 표시하는 값입니다.

구분첫째 마디둘째 마디셋째 마디넷째 마디
마스크 255.255.255.0 11111111 11111111 11111111 00000000
뜻 망 부분 망 부분 망 부분 장비 부분
IP 192.168.10.25 192 168 10 25

앞 세 마디는 마스크가 모두 1이라 망 부분입니다. 그래서 IP의 앞 세 마디 192.168.10이 망 부분입니다.

넷째 마디는 마스크가 모두 0이라 장비 부분입니다. 그래서 25가 장비 부분입니다.

마스크는 망을 관리하는 쪽이 정합니다. 집 공유기나 학원 네트워크 장비가 PC에 IP 주소와 함께 마스크를 나눠 줍니다.

같은 망인지 가리는 법 — 두 주소에서 망 부분만 꺼내 비교합니다. 망 부분이 같으면 같은 망, 하나라도 다르면 다른 망입니다. 장비 부분은 달라도 상관없습니다.

/24이면 앞 세 마디를 비교합니다

주소 A주소 BA의 앞 세 마디B의 앞 세 마디비교결과
192.168.10.25 192.168.10.200 192.168.10 192.168.10 같다 같은 망
192.168.10.25 192.168.20.25 192.168.10 192.168.20 셋째 마디 10 ≠ 20 다른 망

첫째 줄의 넷째 마디 25와 200은 장비 부분이라 달라도 같은 망입니다.

/16이면 앞 두 마디만 비교합니다

주소 A주소 BA의 앞 두 마디B의 앞 두 마디비교결과
192.168.10.25 192.168.20.25 192.168 192.168 같다 같은 망
192.168.10.25 172.16.10.25 192.168 172.16 첫째 마디 192 ≠ 172 다른 망

같은 두 주소 192.168.10.25와 192.168.20.25도 /24에서는 다른 망, /16에서는 같은 망입니다.

마스크가 「앞에서 몇 마디까지 비교할지」를 정하므로, 같은 망인지는 주소가 아니라 마스크가 정합니다.

학원망은 /16입니다 — 서브넷 마스크 255.255.0.0, 기본 게이트웨이 192.168.1.1. 앞 두 마디 192.168이 같으면 같은 망입니다.

집 공유기는 보통 /24입니다 — 서브넷 마스크 255.255.255.0. 앞 세 마디가 같아야 같은 망입니다.


3.2 풀이 순서 — 192.168.10.25/24 하나 풀어 보기

네트워크 주소

192.168.10.25/24가 속한 망에는 192.168.10.0부터 192.168.10.255까지 주소 256개가 있습니다.

이 가운데 가장 작은 주소 192.168.10.0이 네트워크 주소입니다.

장비 하나가 아니라 망 전체를 가리키는 주소입니다. 그래서 PC · 서버 같은 장비의 IP 주소로 설정하지 않습니다.

브로드캐스트 주소 (broadcast)

브로드캐스트는 받는 장비를 한 대로 정하지 않고, 같은 망의 모든 장비에게 한 번에 보내는 방식입니다.

같은 망의 주소 256개 가운데 가장 큰 주소 192.168.10.255가 브로드캐스트 주소입니다.

모두가 받는 주소라서 장비 한 대의 IP 주소로는 설정하지 않습니다.

주소 하나를 받으면 아래 네 단계로 풉니다.

① 마스크 — /24는 255.255.255.0입니다.

② 망 부분 — 255가 앞 세 마디라 192.168.10이 망 부분, 마지막 마디 25가 장비 부분입니다.

③ 네트워크 주소 · 브로드캐스트 주소 — 장비 부분을 0으로 두면 네트워크 주소 192.168.10.0, 255로 두면 브로드캐스트 주소 192.168.10.255입니다.

④ 장비에 줄 수 있는 주소 — 그 둘을 뺀 192.168.10.1 ~ 192.168.10.254, 254개입니다. 기본 게이트웨이도 이 가운데 하나를 씁니다(보통 .1).


컴퓨터는 이렇게 계산합니다 — AND 연산

AND 연산

같은 자리의 비트끼리 비교해서 둘 다 1일 때만 1, 하나라도 0이면 0을 남기는 계산.

1 AND 1 = 1
1 AND 0 = 0
0 AND 0 = 0

IP 주소와 마스크를 2진수로 바꿉니다.

구분첫째 마디둘째 마디셋째 마디넷째 마디
IP 192.168.10.25 11000000 10101000 00001010 00011001
마스크 255.255.255.0 11111111 11111111 11111111 00000000

같은 자리끼리 AND 합니다.

마스크가 1인 자리는 IP의 비트가 그대로 남습니다.

마스크가 0인 자리는 IP의 비트와 상관없이 모두 0이 됩니다.

구분첫째 마디둘째 마디셋째 마디넷째 마디
AND 결과 11000000 10101000 00001010 00000000

결과를 10진수로 다시 바꾸면 192.168.10.0이 나오고, 이것이 네트워크 주소입니다.

다른 주소도 같은 마스크로 AND해서 네트워크 주소가 같으면 같은 망입니다.


3.3 마스크 · CIDR · 주소 수 표

서브넷 마스크2진수CIDR주소 수장비에 쓸 수 있는 수
255.255.0.0 11111111.11111111.00000000.00000000 /16 65,536 65,534
255.255.255.0 11111111.11111111.11111111.00000000 /24 256 254
255.255.255.128 11111111.11111111.11111111.10000000 /25 128 126
255.255.255.192 11111111.11111111.11111111.11000000 /26 64 62
255.255.255.224 11111111.11111111.11111111.11100000 /27 32 30
255.255.255.240 11111111.11111111.11111111.11110000 /28 16 14

쓸 수 있는 수는 주소 수에서 네트워크 주소 · 브로드캐스트 주소 2개를 뺀 수입니다.

CIDR 숫자가 클수록 망이 작아집니다.


3.4 ipaddress로 같은 순서 풀기

ipaddress는 IP 주소와 망을 계산하는 파이썬 모듈. 파이썬에 기본으로 들어 있어 설치가 필요 없습니다.

import ipaddressnet = ipaddress.ip_network("192.168.10.0/24")print(net.netmask)net2 = ipaddress.ip_network("192.168.10.25/24", strict=False)print(net2)
255.255.255.0
192.168.10.0/24
쓰는 것뜻
import ipaddress 파이썬에 들어 있는 모듈 ipaddress를 불러온다
ipaddress.ip_network("주소/CIDR") 그 망을 나타내는 망 객체를 리턴한다
strict=False 망의 첫 주소가 아닌 값을 넣어도 에러 없이, 그 주소가 속한 망을 만든다
망.netmask 서브넷 마스크
망.network_address 네트워크 주소
망.broadcast_address 브로드캐스트 주소

 

4교시 — 서브네팅: 부서마다 필요한 만큼 나누기

왜 필요한가

회사가 192.168.50.0/24(주소 256개)를 받았습니다.

개발팀 · 운영팀처럼 부서마다 다른 망으로 나눠야 서로의 PC에 함부로 닿지 않습니다.

큰 망을 작은 망 여러 개로 나누는 일이 서브네팅입니다.

아무렇게나 나누면 주소가 모자라거나 두 부서가 같은 주소를 받는 사고가 납니다.


4.1 큰 망을 반으로 나누기 — /25

서브네팅 (subnetting)

큰 망을 작은 망 여러 개로 나누는 일.

부서마다 망을 따로 주면 부서 사이 통신을 나눠 관리할 수 있습니다.

블록

큰 망을 나눠 생긴 작은 망 하나.

블록 하나도 망 하나와 같습니다. 가장 작은 주소가 네트워크 주소, 가장 큰 주소가 브로드캐스트 주소입니다.

CIDR서브넷 마스크주소 수장비에 쓸 수 있는 수
/24 255.255.255.0 256 254
/25 255.255.255.128 128 126
/26 255.255.255.192 64 62
/27 255.255.255.224 32 30
/28 255.255.255.240 16 14

/25 블록 하나는 주소 128개입니다.

.0 ──────────────── .127 │ .128 ──────────────── .255
      블록 1 (128개)      │      블록 2 (128개)
블록범위네트워크 주소브로드캐스트 주소장비에 줄 수 있는 주소쓸 수 있는 수
192.168.50.0/25 .0 ~ .127 .0 .127 .1 ~ .126 126
192.168.50.128/25 .128 ~ .255 .128 .255 .129 ~ .254 126

블록은 쓸 수 있는 수(126)가 아니라 주소 수(128)만큼씩 자릅니다.

CIDR한 블록의 주소 수블록 수블록이 시작하는 주소
/25 128개 2개 .0 · .128
/26 64개 4개 .0 · .64 · .128 · .192

🐍 .subnets(new_prefix=…)

import ipaddress

parent = ipaddress.ip_network("192.168.50.0/24")

for block in parent.subnets(new_prefix=26):
    print(block)
192.168.50.0/26
192.168.50.64/26
192.168.50.128/26
192.168.50.192/26

4.2 설계 검증 — 용량과 겹침

겹침 (overlap)

두 부서의 망이 같은 주소를 함께 가진 상태.

같은 주소를 두 부서 장비가 받으면 주소 충돌로 통신이 끊기고, 로그의 주소로 어느 장비인지 가릴 수 없습니다.

설계표를 만들면 두 가지를 확인합니다.

  • 용량 — 부서 망의 「쓸 수 있는 수」가 장비 수 이상인가
  • 겹침 — 두 부서의 망이 같은 주소를 갖지 않는가

🐍 .overlaps() — 두 망이 겹치는지

import ipaddress

a = ipaddress.ip_network("192.168.50.0/26")
b = ipaddress.ip_network("192.168.50.64/26")
c = ipaddress.ip_network("192.168.50.0/25")

print(a.overlaps(b))
print(a.overlaps(c))
False
True

망1.overlaps(망2)는 같이 가진 주소가 하나라도 있으면 True, 없으면 False를 리턴합니다.


 

5교시 — 스위치와 라우터 · 다음 홉

왜 필요한가

PC는 패킷을 보내기 전에 목적지가 같은 망인지부터 계산합니다.

같으면 스위치로 바로, 다르면 기본 게이트웨이로 보냅니다.

관제에서 「같은 층 PC끼리는 되는데 다른 부서로만 안 된다」 같은 문의는 대부분 이 판정에서 원인이 나옵니다.

게이트웨이를 다른 망 주소로 잘못 설정하면 물리적 연결(랜선 · Wi-Fi)이 정상이어도 다른 망(인터넷)으로 패킷을 보내지 못합니다.


5.1 두 장비가 하는 일

MAC 주소 표

스위치가 기억하는 「이 MAC 주소는 몇 번 포트에 있다」 표.

프레임이 들어오면 보낸 쪽 MAC과 들어온 포트를 적어 둡니다.

다음부터는 그 MAC으로 가는 프레임을 그 포트로만 보냅니다.

구분스위치라우터
층 2층(데이터링크) 3층(네트워크)
보는 주소 MAC 주소 IP 주소
잇는 범위 같은 망의 장비들 다른 망 사이
기억하는 표 MAC 주소 표 라우팅 표

5.2 같은 망이면 스위치로, 다른 망이면 게이트웨이로

다음 홉 (next hop)

지금 장비가 패킷을 바로 넘겨줄 다음 장비.

패킷은 목적지까지 장비를 하나씩 건너가는데, 장비마다 「다음은 누구」만 알면 됩니다.

PC의 다음 홉은 보통 기본 게이트웨이(공유기)입니다.

ARP

IP 주소를 보고 그 장비의 MAC 주소를 알아내는 방법.

내 PC가 알아 둔 「IP 주소 → MAC 주소」 목록을 ARP 캐시라고 하고, arp -a로 봅니다.

부서망게이트웨이
개발팀 192.168.10.0/24 192.168.10.1
운영팀 192.168.20.0/24 192.168.20.1
보안팀 192.168.30.0/24 192.168.30.1

개발팀 PC 192.168.10.25가 보낼 곳은 이렇게 정합니다.

목적지셋째 마디보낼 곳프레임의 목적지 MAC
192.168.10.30 10 = 10 — 같은 망 스위치로 직접 .30 PC의 MAC
192.168.30.5 10 ≠ 30 — 다른 망 게이트웨이 192.168.10.1 게이트웨이의 MAC
8.8.8.8 첫째 마디부터 다르다 게이트웨이 192.168.10.1 게이트웨이의 MAC

다른 망으로 보낼 때 패킷의 두 주소는 이렇게 됩니다.

필드값바뀌나
목적지 IP 192.168.30.5 끝까지 그대로
목적지 MAC 게이트웨이의 MAC 장비를 하나 지날 때마다 바뀐다

PC는 ARP로 같은 망의 장비에게만 MAC을 묻습니다.

다른 망으로 보낼 때는 게이트웨이의 MAC만 알면 됩니다.


5.3 스위치가 프레임을 받으면

프레임 (frame)

2계층에서 주고받는 데이터 묶음.

맨 앞에 보낸 쪽 · 받는 쪽 MAC 주소가 적혀 있고, 스위치는 이 MAC을 보고 포트를 고릅니다.

플러딩 (flooding)

목적지 MAC이 MAC 주소 표에 없을 때 들어온 포트를 뺀 모든 포트로 복사해 보내는 것.

답이 돌아오면 스위치가 그 MAC의 포트를 배우고, 그다음부터는 그 포트로만 보냅니다.

스위치는 프레임이 들어올 때마다 아래 세 단계를 합니다.

① 출발지를 적는다 — 보낸 MAC과 들어온 포트를 MAC 주소 표에 적습니다.

② 목적지를 찾는다 — 받는 MAC이 표에 있는지 봅니다.

③ 내보낸다 — 표에 있으면 그 포트 하나로만, 없으면 들어온 포트를 뺀 모든 포트로 플러딩합니다.

들어온 프레임① 표에 적는 것② 받는 MAC이 표에 있나③ 내보내는 포트그 뒤 MAC 주소 표
2번 포트로 CC → DD CC는 2번 DD 없음 1 · 3 · 4번 CC:2
4번 포트로 DD → CC DD는 4번 CC 있음(2번) 2번 하나로만 CC:2 · DD:4

스위치가 배우는 것은 출발지입니다.

🐍 딕셔너리로 MAC 주소 표 만들기

mac_table = {}

mac_table["CC"] = 2
print(mac_table)

print("CC" in mac_table)
print("DD" in mac_table)
print(mac_table["CC"])
{'CC': 2}
True
False
2
쓰는 것뜻
{} 빈 딕셔너리를 만든다
d["CC"] = 2 키 "CC"에 값 2를 넣는다
"CC" in d 키 "CC"가 있으면 True
d["CC"] 키 "CC"의 값을 꺼낸다. 없는 키면 KeyError

 

6교시 — VLAN · 라우팅 표 · 기본 경로

왜 필요한가

부서마다 스위치를 따로 사면 비쌉니다.

스위치 하나를 여러 망으로 나누는 것이 VLAN입니다.

VLAN이 다르면 같은 스위치에 꽂혀 있어도 직접 못 주고받습니다.

개발팀 PC가 감염돼도 보안팀 망으로 바로 퍼지지 않습니다.

VLAN 사이를 오가려면 라우터가 필요하고, 라우터는 라우팅 표를 보고 경로를 고릅니다.

내 PC에도 라우팅 표가 있습니다.

홉 (hop)

패킷이 목적지까지 가면서 거쳐 가는 라우터 하나를 홉 하나로 셉니다.


6.1 VLAN — 스위치 하나를 여러 망으로

LAN (Local Area Network)

건물 · 층처럼 가까운 곳의 장비를 묶은 망.

같은 LAN의 장비끼리는 스위치가 MAC 주소로 프레임을 전달합니다.

VLAN (Virtual LAN)

스위치 포트마다 번호를 정해 스위치 하나를 여러 망으로 나눈 것.

VLAN이 다르면 같은 스위치에 꽂혀 있어도 서로 다른 망이라 직접 통신하지 못합니다.

부서마다 VLAN을 나누면 브로드캐스트가 그 부서에만 퍼집니다.

예:

개발팀 = VLAN 10 = 192.168.10.0/24
운영팀 = VLAN 20 = 192.168.20.0/24
보안팀 = VLAN 30 = 192.168.30.0/24

VLAN은 스위치가 포트를 나누는 번호이고, 서브넷은 PC가 쓰는 IP 주소의 범위입니다.

액세스 포트 (access port)

PC · 프린터 같은 장비를 꽂는 포트.

포트마다 VLAN 번호가 하나씩 정해져 있습니다.

예:

1번 포트 → VLAN 10
2번 포트 → VLAN 20

VLAN 번호는 스위치에서만 설정합니다. PC에서는 따로 설정하지 않습니다.

개발팀 PC가 보낸 브로드캐스트는 VLAN 10 포트에만 퍼지고, VLAN 20 · 30 포트에는 가지 않습니다.

VLAN이 다른 장비끼리는 같은 스위치에 꽂혀 있어도 라우터를 거쳐야 오갑니다.

⚠ 서로 다른 VLAN에 같은 서브넷을 주면, PC는 같은 망이라 생각해 게이트웨이로 안 보내고, 스위치는 VLAN이 달라 막습니다 — 라우터가 있어도 안 됩니다.


6.2 라우팅 표와 기본 경로

라우팅 표 (routing table)

「이 망으로 가려면 다음 홉은 누구」를 적은 표.

라우터와 내 PC 모두 가지고 있습니다.

패킷의 목적지 주소에 맞는 줄을 찾아 그 줄의 다음 홉으로 보냅니다.

게이트웨이 (gateway)

내 망 밖으로 나가는 패킷을 받아 다른 망으로 보내 주는 라우터의 주소입니다.

기본 경로 (default route)

라우팅 표의 0.0.0.0/0 줄.

/0은 비교할 마디가 하나도 없다는 뜻이라 모든 주소에 맞습니다.

개발팀 PC 192.168.10.25의 라우팅 표는 다음과 같습니다.

목적지 망다음 홉목적지가 이렇다면PC가 하는 일예
192.168.10.0/24 직접 연결 앞 세 마디가 192.168.10 내 망이라 스위치로 바로 보낸다 192.168.10.30
0.0.0.0/0 192.168.10.1 위 줄에 맞지 않는 나머지 전부 게이트웨이에 넘긴다 8.8.8.8 · 192.168.20.5

0.0.0.0/0은 「위에서 맞지 않은 나머지는 전부 여기로」라는 줄이 되고, 이 줄을 기본 경로라고 부릅니다.


ipconfig와 tracert

ipconfig의 「기본 게이트웨이」는 내 PC가 「나머지 전부」를 넘기는 장비입니다.

tracert -d -h 5 8.8.8.8

tracert는 목적지까지 거쳐 가는 장비를 다음 홉 순서대로 보여 줍니다.

최대 5홉 이상의
8.8.8.8(으)로 가는 경로 추적

  1    <1 ms    <1 ms    <1 ms  192.168.1.1
  2     2 ms     1 ms     1 ms  10.10.0.1
  3     *        *        *     요청 시간이 만료되었습니다.
  4     5 ms     4 ms     4 ms  112.188.0.1
  5     6 ms     5 ms     6 ms  112.174.0.1
부분뜻
맨 앞 숫자 1 · 2 … 몇 번째 다음 홉인가
가운데 ms 세 개 같은 홉에 세 번 보내 답이 오기까지 걸린 시간
<1 ms 1밀리초보다 짧게 걸렸다는 뜻
맨 끝 주소 그 홉의 라우터 주소
* * * 그 장비가 답하지 않도록 막아 둔 것. 길이 끊긴 것이 아니다

경로 확인 — 2번 · 3번 줄 주소를 차례로 보면 학원 공유기 → 통신사 라우터 → … 순서로 간다는 것을 알 수 있습니다.

관제 · 장애 대응 — 어느 줄부터 주소가 안 나오고 끝까지 끊기는지 보면 문제가 생긴 구간을 좁힐 수 있습니다.


6.3 회사 라우터의 라우팅 표

목적지 망다음 홉
192.168.10.0/24 직접 연결 VLAN 10
192.168.20.0/24 직접 연결 VLAN 20
192.168.30.0/24 직접 연결 VLAN 30
0.0.0.0/0 인터넷 쪽 라우터

목적지가 들어가는 회사 망 줄이 있으면 그 줄로 보냅니다.

회사 망 줄에 하나도 맞지 않으면 기본 경로 0.0.0.0/0 줄로 인터넷 쪽 라우터에 보냅니다.

🐍 튜플 리스트

routes = [    ("192.168.10.0/24", "VLAN 10"),    ("192.168.20.0/24", "VLAN 20")]for net, hop in routes:    print(net, "→", hop)
192.168.10.0/24 → VLAN 10
192.168.20.0/24 → VLAN 20

🐍 ipaddress.ip_address() · 주소 in 망

import ipaddressnet = ipaddress.ip_network("192.168.30.0/24")pc = ipaddress.ip_address("192.168.30.5")web = ipaddress.ip_address("8.8.8.8")print(pc in net)print(web in net)
True
False

🐍 found = False → found = True

found = Falsefor vlan in [10, 20, 30]:    if vlan == 30:        found = Trueprint(found)
True
쓰는 것뜻
found = False 반복 전에 「아직 못 찾음」으로 시작한다
found = True 찾았을 때 바꾼다
if found == False: 반복이 끝나도 한 번도 못 찾았으면 실행한다

 

7교시 — 오늘 배운 것 다시 풀기 · 설계서 올리기

왜 필요한가

관제 업무에서는 매번 처음 보는 주소가 나옵니다.

주소가 바뀌어도 오늘 배운 순서 그대로 풀 수 있는지 확인하고, 다 채운 설계서를 깃허브에 올립니다.

설계서

주소 · VLAN · 게이트웨이 · 경로를 정리해 적은 문서.

오늘은 보고서 day02_subnet_design.md가 설계서이고, 맨 아래 「최종 설계서」에 회사망을 정리합니다.

🐍 f-string · strict=False · 망 객체

import ipaddressip = "192.168.20.7"cidr = f"{ip}/24"net = ipaddress.ip_network(cidr, strict=False)print(cidr)print(net.network_address)
192.168.20.7/24
192.168.20.0
쓰는 것뜻
f"{ip}/24" f-string — {} 안의 변수 값을 글자에 넣는다
ipaddress.ip_network("주소/CIDR", strict=False) 그 주소가 속한 망 객체를 만든다
.netmask 서브넷 마스크
.network_address 네트워크 주소
.broadcast_address 브로드캐스트 주소

 

 

너무 피곤해요,, 오늘 약간 몰아친 느낌,,,,

그럼 아됴,,

안녕하세요

오늘부터 새로운 과정을 나가게 되었어요,,,

일단 모닝 30분과제부터 스타트,,

 

 

1. 왜 이걸 설치하는지 찾아봅니다

1.1 보안 담당자는 왜 패킷을 직접 열어 볼까요?

> 보안 담당자가 패킷을 직접 열어 보는 이유는 로그나 경고(Alert)만으로는 확인할 수 없는 네트워크 통신의 실제 데이터와 이상 행위를 가장 정확하고 원본 그대로 파악하기 위해서이다.

2. 이 말들이 무슨 뜻인지 찾아봅니다

2.1 패킷 (packet)

> 패킷(Packet)은 네트워크로 보내는 데이터의 전송 단위(조각), 대용량의 데이터를 그대로 보내면 오류가 났을 때 전체를 다시 보내야 하고 회선이 독점되므로, 다루기 쉬운 작은 크기로 쪼갠 데이터 덩어리를 말한다.

2.2 Npcap(엔피캡)

> Nmap 프로젝트에서 개발한 윈도우용 네트워크 패킷 캡처 및 전송 라이브러리 , Npcap(엔피캡)은 Wireshark(와이어샤크)가 윈도우 환경에서 네트워크 패킷을 수집하고 분석할 수 있도록 돕는 필수 패킷 캡처 라이브러리(드라이버)입니다.

 

 

그리구 Wireshark를 깔았습니당!


오늘의 순서

교시무엇사용하는 것
2교시 첫 관찰 — 내 PC에서 서버까지 닿나 ping
3교시 OSI 7계층 ① 아래 세 층 — MAC 주소와 IP 주소 ipconfig /all · arp -a
4교시 OSI 7계층 ② 위 네 층 — 포트 번호와 지금의 연결 netstat
5교시 TCP와 UDP · 연결을 맺는 세 번의 신호(3-way handshake) · 끝내는 네 번 파이썬 · netstat
6교시 Wireshark 실행 · 첫 캡처 · 화면 읽기 · 필터 Wireshark
7교시 내 연결을 직접 잡아 보고서로 남기기 Wireshark · curl · 파이썬

 

2교시 — 첫 관찰: ping

왜 필요한가

「인터넷이 안 돼요」가 오면 관제는 먼저 닿는지부터 봅니다. 그 첫 명령이 ping입니다.

ping은 상대에게 「거기 있어요?」 하고 작은 신호를 보내고, 답이 오는 데 걸린 시간을 잽니다.

답이 오면 상대까지 패킷이 오가고 있다는 뜻입니다. 안 오면 어느 단계에서 끊겼는지 좁혀 갑니다.

여기 나오는 말

용어뜻
패킷 (packet) 보낼 데이터를 작게 나눈 한 조각. 받는 쪽 주소가 적혀 있다
ping (핑) 상대에게 신호를 보내 답이 오는지와 걸린 시간을 재는 명령
라우터 (router) 망과 망을 잇고, 패킷에 적힌 주소를 보고 다음 장비로 넘겨 주는 장비
공유기 라우터 기능에 더해 여러 기기가 인터넷 주소 하나를 나눠 쓰게 해 주고 와이파이까지 한 상자에 담은 장비
DNS (디엔에스) google.com 같은 이름을 IP 주소로 바꿔 주는 시스템
TTL (Time To Live) 패킷이 앞으로 거칠 수 있는 장비 수. 라우터·공유기를 하나 지날 때마다 1씩 줄어든다

비트 · 바이트 · 16진수

데이터의 크기는 비트와 바이트로 세고, 주소는 16진수로 짧게 적습니다.

용어무엇인가오늘 어디서 보나
비트 (bit) 0 또는 1이 들어가는 한 칸으로, 가장 작은 단위입니다 1계층에서 선을 타고 가는 전기 신호
바이트 (byte) 비트 8개를 묶은 단위입니다 ping의 32바이트 데이터 사용 · Wireshark의 패킷 길이
16진수 (hexadecimal) 0~9와 A~F 열여섯 개로 수를 적는 방법입니다 MAC 주소 A4-B1-C2-… · Wireshark의 바이트 칸

MAC 주소는 6바이트 = 48비트, IPv4 주소는 4바이트 = 32비트입니다.

ping은 어떻게 갔다가 돌아오는가

내 PC
  ↓
기본 게이트웨이(공유기)
  ↓
인터넷의 라우터 여러 대
  ↓
목적지 서버
  ↓
응답
  ↓
내 PC

내 PC가 신호를 만들어 공유기로 넘기고, 인터넷의 라우터들이 차례로 넘겨 목적지 서버까지 보냅니다.

목적지 서버가 응답을 만들면 다시 내 PC로 돌아오고, PC가 왕복 시간을 표시합니다.

ping 결과 읽기

Ping 8.8.8.8 32바이트 데이터 사용:

8.8.8.8의 응답: 바이트=32 시간=34ms TTL=115
8.8.8.8의 응답: 바이트=32 시간=33ms TTL=115
8.8.8.8의 응답: 바이트=32 시간=35ms TTL=115
8.8.8.8의 응답: 바이트=32 시간=34ms TTL=115

패킷: 보냄 = 4, 받음 = 4, 손실 = 0 (0% 손실)
최소 = 33ms, 최대 = 35ms, 평균 = 34ms
보이는 것뜻
8.8.8.8의 응답 답이 왔다 — 상대까지 패킷이 오간다
시간=34ms 갔다가 돌아오는 데 걸린 왕복 시간
TTL=115 응답이 도착했을 때 남아 있던 TTL
손실 = 0 (0% 손실) 보낸 것 가운데 돌아오지 않은 비율
평균 = 34ms 네 번의 왕복 시간 평균

TTL은 같은 대상끼리 비교합니다. 시작값이 서버마다 다르기 때문에 다른 서버의 TTL 숫자를 단순 비교하지 않습니다.

ping에 응답이 없다고 서버가 꺼진 것은 아니다

ping은 ICMP(Internet Control Message Protocol)를 사용합니다.

보안을 위해 ICMP에 응답하지 않도록 설정한 서버나 방화벽도 많습니다.

따라서 ping은 실패하지만 웹 사이트는 정상적으로 열릴 수도 있습니다.

이 경우 「서버 다운」이라고 단정하기보다 **「ICMP로는 확인 불가」**로 판단합니다.

ping 명령

ping 8.8.8.8
ping -n 10 8.8.8.8
ping -t 8.8.8.8
옵션뜻
-n 숫자 보낼 횟수
-t 멈출 때까지 계속
Ctrl + C 실행 중인 명령을 멈춘다

 

3교시 — OSI 7계층 ① 아래 세 층: MAC과 IP

왜 필요한가

네트워크는 하는 일을 일곱 층으로 나눠 놓았습니다.

층마다 맡은 일이 달라서 문제가 생기면 층을 나눠 원인을 찾을 수 있습니다.

3교시에는 아래 세 층을 봅니다.

  • 1층: 신호
  • 2층: 같은 망 안의 전달
  • 3층: 다른 망까지 경로 선택

2층과 3층에는 각각 주소가 있습니다. MAC 주소와 IP 주소를 구별하는 것이 핵심입니다.

OSI 7계층

층이름하는 일쓰는 주소 · 단위
7 응용 사용자가 쓰는 서비스 — 웹 · 메일 · DNS URL · 도메인 이름
6 표현 암호화 · 압축 · 글자 형식 —
5 세션 대화의 시작과 끝을 관리 —
4 전송 어느 프로그램에 줄지 — TCP · UDP 포트 번호
3 네트워크 다른 망에 있는 상대까지 경로 선택 IP 주소
2 데이터링크 같은 망 안에서 옆 장비로 전달 MAC 주소
1 물리 전기 · 빛 · 전파 신호 랜선 · Wi-Fi

실무에서는 5·6·7층을 묶어 「응용 계층」으로 부르는 TCP/IP 4계층도 많이 씁니다.

IP 주소와 MAC 주소

구분IP 주소MAC 주소
층 3층 2층
모양 192.168.0.15 A4-B1-C2-D3-E4-F5
누가 정하나 망에 들어갈 때 받는다 랜카드·Wi-Fi 장치에 정해져 나온다
장소가 바뀌면 바뀔 수 있다 그대로
쓰는 범위 멀리 있는 상대까지 같은 망 안

한 PC에 MAC 주소가 여러 개 있을 수 있습니다. 랜·Wi-Fi·가상 장치마다 하나씩 있습니다.

이더넷

이더넷(Ethernet)은 랜선으로 기기를 잇는 유선 연결 방식입니다.

2계층에서 MAC 주소를 이용해 전달합니다.

ipconfig 결과에서 「이더넷 어댑터」로 확인할 수 있습니다.

ipconfig

ipconfig
ipconfig /all
항목뜻
IPv4 주소 현재 사용하는 IP 주소
서브넷 마스크 어디까지가 같은 망인지 정하는 값
기본 게이트웨이 다른 망으로 가는 패킷을 받아 넘기는 장비의 주소
물리적 주소 MAC 주소

물리적 주소는 ipconfig /all에서 확인할 수 있습니다.

ARP

ARP(Address Resolution Protocol)는 같은 망에서

「이 IP의 MAC 주소가 뭐예요?」

하고 묻는 방법입니다.

예를 들어 PC가 공유기 192.168.1.1로 패킷을 보내려면 공유기의 MAC 주소를 알아야 합니다.

① 공유기의 IP 주소는 앎
   ↓
② 같은 망 전체에 MAC 주소를 물어봄
   ↓
③ 해당 IP를 가진 공유기가 자신의 MAC 주소를 답함
   ↓
④ PC가 IP ↔ MAC 정보를 저장함

저장해 둔 표가 ARP 캐시입니다.

arp -a

예:

인터넷 주소        물리적 주소              유형
192.168.0.1       a8-5e-45-01-2b-3c       동적
192.168.0.255     ff-ff-ff-ff-ff-ff       정적

동적은 ARP로 알아내 자동으로 적은 항목입니다.

게이트웨이 줄의 MAC 주소가 평소와 다르면 ARP 위장 공격을 의심할 수 있습니다.

네트워크 문제는 아래 층부터 확인한다

아래 층이 동작해야 위 층이 동작합니다.

원인 후보계층
서버의 서비스가 꺼짐 7계층
IP 주소 설정이 틀림 · 공유기 밖으로 못 나감 3계층
같은 망 안의 전달이 안 됨 2계층
랜선이 빠짐 · Wi-Fi가 꺼짐 1계층

 

4교시 — OSI 7계층 ② 위 네 층: 포트와 지금의 연결

왜 필요한가

IP 주소는 어느 컴퓨터인지까지만 알려 줍니다.

한 컴퓨터에서는 브라우저·메신저·업데이트 같은 프로그램이 동시에 통신합니다.

그중 어느 프로그램에 줄지는 4층의 포트 번호가 정합니다.

여기 나오는 말

용어뜻
포트 번호 (port) 한 컴퓨터 안에서 어느 프로그램에게 줄지 정하는 번호(0~65535)
서비스 그 포트에서 일하는 프로그램의 종류
netstat 지금 내 PC의 연결 목록을 보여 주는 명령
LISTENING 내 PC가 연결 요청을 기다리며 열어 둔 상태
ESTABLISHED 상대와 연결되어 대화 중인 상태
PID 실행 중인 프로그램마다 붙는 번호

자주 보는 포트

포트서비스쓰임
22 SSH 서버 원격 접속
53 DNS 이름 → IP 주소
80 HTTP 암호화하지 않은 웹
443 HTTPS 암호화된 웹
3389 RDP Windows 원격 데스크톱
135 RPC Windows가 다른 PC의 기능을 원격으로 사용할 때
445 SMB Windows 파일·프린터 공유

netstat

netstat -n
netstat -ano
옵션뜻
-n 연결을 숫자 주소로 보여 준다
-a LISTENING 포트까지 모두 보여 준다
-o 연결을 가진 프로그램의 PID를 보여 준다
tasklist

tasklist는 실행 중인 프로그램과 PID를 보여 줍니다.

LISTENING 상태의 포트는 외부에서 접속을 시도할 수 있는 지점이기 때문에 사용하지 않는 포트는 닫습니다.

캡슐화

보낼 데이터 앞에 층마다 포트·IP·MAC 정보를 붙여 패킷을 만드는 것을 **캡슐화(encapsulation)**라고 합니다.

웹 요청
  ↓
+ 포트 번호    4계층
  ↓
+ IP 주소      3계층
  ↓
+ MAC 주소     2계층
  ↓
전기·전파      1계층

받는 쪽에서는 반대로 아래에서 위로 올라가며 정보를 하나씩 떼어 내고 알맞은 프로그램에 넘깁니다.


 

5교시 — TCP와 UDP · 3-way handshake

TCP와 UDP

구분TCPUDP
연결 보내기 전에 연결을 맺는다 연결 없이 바로 보낸다
받았는지 확인 한다 UDP 자체는 하지 않는다
순서 번호로 순서를 맞춘다 맞추지 않는다
속도 확인하느라 조금 느리다 빠르다
쓰는 곳 웹 · 파일 · 메일 · SSH DNS 조회 · 실시간 영상 · 게임 · 음성 통화

ping은 TCP도 UDP도 아니고 ICMP라는 별도 방식을 사용합니다.

TCP 플래그

TCP 패킷에는 여러 플래그가 함께 켜질 수 있습니다.

flags = ["SYN", "ACK"]print("SYN" in flags)   # Trueprint(flags == "SYN")   # False

리스트 안에 특정 플래그가 있는지 확인할 때는 in을 사용합니다.

플래그뜻
SYN 연결하자
ACK 받았다
FIN 나는 다 보냈다
RST 절차 없이 바로 끊는다

3-way handshake

TCP는 데이터를 보내기 전에 세 번 신호를 주고받아 연결을 맺습니다.

내 PC                              서버

 │ ─── ① SYN ──────────────────▶ │
 │ ◀── ② SYN, ACK ───────────── │
 │ ─── ③ ACK ──────────────────▶ │
 │                                │
 │       ESTABLISHED              │

②가 SYN, ACK인 이유는 서버가

  • 「네 SYN을 받았다」는 ACK
  • 「나도 연결하자」는 SYN

두 가지를 한 패킷에 같이 보내기 때문입니다.

연결을 끝낼 때

내 PC                              서버

 │ ─── FIN ─────────────────────▶ │
 │ ◀── ACK ───────────────────── │
 │ ◀── FIN ───────────────────── │
 │ ─── ACK ─────────────────────▶ │

연결을 맺을 때는 세 번이지만 끝낼 때는 네 번입니다.

서버는 ACK는 바로 보낼 수 있지만 아직 보낼 데이터가 남아 있다면 FIN은 나중에 보내야 하기 때문입니다.

끝난 연결은 바로 사라지지 않고 잠시 TIME_WAIT 상태로 남을 수 있습니다.

연결 하나를 구분하는 네 값

값예
출발지 IP 192.168.0.15
출발지 포트 51234
목적지 IP 203.0.113.10
목적지 포트 443

내 PC 쪽의 큰 포트 번호는 연결할 때 운영체제가 자동으로 고르는 임시 포트입니다.

Windows에서는 보통 49152~65535 범위를 사용합니다.


 

6교시 — Wireshark 실행 · 첫 캡처 · 필터

왜 필요한가

5교시의 SYN·ACK는 지금까지 그림이었습니다.

Wireshark는 내 PC를 지나는 패킷을 붙잡아 실제 값으로 보여 줍니다.

인터페이스

Wireshark에서 패킷을 잡으려면 먼저 인터페이스를 골라야 합니다.

인터페이스는 Wi-Fi나 이더넷처럼 PC를 네트워크에 연결하는 통로입니다.

인터페이스 목록이 비어 있다면 Wireshark가 패킷을 잡도록 돕는 Npcap이 설치되어 있는지 확인합니다.

캡처

인터페이스를 더블클릭하면 캡처가 시작되고 패킷이 한 줄씩 쌓입니다.

캡처한 전체 개수는 화면 아래 Packets에서 확인합니다.

패킷 한 줄 읽기

열뜻
Source 보낸 쪽 IP 주소
Destination 받는 쪽 IP 주소
Protocol TCP · UDP · DNS · TLS 등의 통신 방식
Info 포트 번호와 SYN·ACK 등의 요약

예:

Source          Destination     Protocol   Info

192.168.0.15    203.0.113.10    TCP        51234 → 443 [SYN]
203.0.113.10    192.168.0.15    TCP        443 → 51234 [SYN, ACK]
192.168.0.15    203.0.113.10    TCP        51234 → 443 [ACK]

51234 → 443은 내 PC에서 서버로 보내는 방향이고,

443 → 51234는 서버에서 내 PC로 돌아오는 방향입니다.

패킷의 상세 정보

패킷에는 층마다 헤더가 붙습니다.

  • Ethernet II — 2층의 MAC 주소
  • Internet Protocol Version 4 — 3층의 출발지·목적지 IP와 TTL
  • Transmission Control Protocol — 4층의 포트와 플래그

표시 필터

캡처하면 수백 줄의 패킷이 쌓이므로 필요한 패킷만 보이도록 **표시 필터(display filter)**를 사용합니다.

tcp
udp
dns
http
tcp.port == 443
ip.addr == 203.0.113.10
tcp.flags.syn == 1
tcp.flags.fin == 1
dns || tcp.port == 443
필드뜻
ip.src 출발지만
ip.dst 목적지만
ip.addr 출발지든 목적지든 모두

필터를 걸어도 패킷이 삭제되지는 않습니다.

전체 Packets 숫자는 그대로이고 조건에 맞는 Displayed 숫자만 달라집니다.

필터 칸이 초록색이면 문법이 맞고, 빨간색이면 오타가 있습니다.


 

7교시 — 내 연결을 직접 잡아 보고서로 남기기

왜 필요한가

6교시에는 여러 패킷 속에서 통신을 관찰했습니다.

이번에는 내가 만든 연결 하나만 잡아서

  • 열리는 순간 — 3-way handshake
  • 내용 — HTTP
  • 닫히는 순간 — FIN

을 찾아 값으로 남깁니다.

캡처 필터와 표시 필터

구분표시 필터캡처 필터
언제 패킷을 잡은 뒤 잡기 전
역할 보여 줄 패킷만 고른다 조건에 맞는 패킷만 처음부터 잡는다
예 tcp.port == 80 tcp port 80

두 필터는 문법이 다릅니다.

HTTP 연결 잡기

캡처 필터:

tcp port 80

HTTP는 포트 80을 사용하고 암호화하지 않기 때문에 내용을 직접 확인하기 좋습니다.

브라우저는 HTTP를 HTTPS로 바꾸는 경우가 있으므로 터미널의 curl을 사용합니다.

curl "http://example.com"

HTTP와 HTTPS

구분HTTP (80)HTTPS (443)
암호화 하지 않음 TLS로 암호화
3-way handshake 보임 똑같이 보임
그 뒤 내용 GET /... 같은 내용이 보임 TLSv1.3, Application Data 등으로 보임
관제에서 내용까지 확인 가능 누구와 · 언제 · 얼마나 통신했는지 확인

HTTPS도 먼저 TCP의 3-way handshake로 연결을 맺은 뒤 그 위에서 TLS 암호화를 시작합니다.

HTTP는 보낸 글자가 그대로 보이기 때문에 로그인 화면이 HTTP라면 비밀번호 같은 내용도 패킷에서 보일 수 있습니다.

그래서 요즘 웹에서는 HTTPS를 사용합니다.

상태 코드

상태 코드는 서버가 응답 맨 앞에 붙이는 숫자입니다.

200 → 성공
404 → 없음

Wireshark에서 HTTP 패킷을 확인하면 다음과 같은 응답을 볼 수 있습니다.

HTTP/1.1 200 OK

한눈에 정리

확인하려는 것사용하는 것
상대까지 닿는가 ping
내 IP·게이트웨이는 무엇인가 ipconfig
내 MAC 주소는 무엇인가 ipconfig /all
IP와 MAC의 대응은 무엇인가 arp -a
지금 어떤 포트로 연결되어 있는가 netstat -ano
어떤 프로그램이 연결을 사용하고 있는가 PID · tasklist
TCP 연결은 어떻게 시작되는가 SYN → SYN, ACK → ACK
실제 패킷에는 무엇이 들어 있는가 Wireshark
필요한 패킷만 보고 싶은가 표시 필터
처음부터 필요한 패킷만 잡고 싶은가 캡처 필터
HTTP 요청을 직접 만들고 싶은가 curl
 
오늘은 네트워크 첫 시간이였습니다..
다시 공부하는 시간이 정말 좋았구요(정말?)
연휴 즐기고 첫 평일이라 너무 피곤했는데 .. 오늘 꼭 일찍 자고 내일 더 깨끗한 정신으로 수업을 들어야겠어요,,
그럼 아됴,,

안녕하세요

벌써 AI 마지막 날이네요,, 그리구 내일부터 3일간 휴일입니다!!!!!

그럼 아침 30분 과제 레츠고,

 

1. 왜 이걸 하는지 찾아봅니다

1.1 git 으로 올릴 때는 왜 add · commit · push 세 단계로 나눌까요?

> 작업을 선택하고(Add), 기록을 확정하며(Commit), 원격 서버에 공유하는(Push) 역할과 책임이 명확히 분리되어 있기 때문이다.

commit(커밋)은 파일이나 폴더의 변경 사항을 로컬 저장소에 영구적으로 기록하고 저장하는 행위이다.

2. 이 말들이 무슨 뜻인지 찾아봅니다

2.1 commit (커밋)

> commit(커밋)은 파일이나 폴더의 변경 사항을 로컬 저장소에 영구적으로 기록하고 저장하는 행위

깃(Git)에서 커밋(Commit)은 프로젝트의 '스냅샷(특정 시점의 상태)'을 남기는 것

마치 게임을 하다가 중요한 순간에 저장(Save)을 하는 것과 같다. 파일의 추가, 수정, 삭제 등 변경된 모든 내역이 커밋에 기록됨.

커밋을 할 때 메시지(Commit Message)를 함께 적는 이유 1) 변경 이유 기록 2) 원활한 협업 3) 버그 추적 및 관리

 

2.2 push (푸시)

> push(푸시)는 내 PC(로컬 저장소)에 있는 소스 코드나 파일의 변경 사항을 원격 서버(원격 저장소, 주로 GitHub 등)로 보내는 것

보통 내 PC에서 커밋(Commit)한 변경 사항을 인터넷이나 외부 네트워크에 있는 원격 저장소(Remote Repository)로 보낸다.

 

 

 

 

 

오늘의 순서

교시무엇만드는 파일

2교시 설정 분리 — 바뀌는 값은 파일 하나에 config.json
3교시 알림 연동 — 실패해도 멈추지 않게 notifier.py
4교시 하나로 잇기 — 명령 한 번에 끝까지 pipeline.py
5교시 코드 리뷰와 단위 테스트 test_agent_core.py
6교시 디버깅 — 에러가 있는 코드 고치기 —
7교시 완성과 회고 · 깃허브에 올리기 docs/day08_retrospective.md

 


설정 분리 — config.json

설정 분리

설정값을 분리하는 주요 방법은 설정의 규모와 보안 요구사항에 따라 나뉜다.

  • config.py 파일 — 상수 정의
  • 환경별 파일 분리 — Development / Production
  • 외부 설정 파일 — YAML, JSON, INI
  • 환경 변수 — os.environ, .env, python-decouple

코드와 설정(Configuration)을 분리하는 것은 프로그램의 유지보수성과 보안을 높이기 위한 핵심 설계 원칙.

하드코딩

프로그램 소스 코드 내부에 데이터나 값(상수, 파일 경로, API 키 등)을 직접 고정하여 입력하는 방식.

유지보수, 보안, 협업 등 다양한 측면에서 큰 불편함과 리스크가 발생한다.

  • 유지보수가 힘들어진다.
  • 보안에 치명적.
  • 환경 변화에 유연하게 대처할 수 없음.
  • 테스트하기 어려움.

왜 필요한가

지금 모델 이름은 llm_client.py 안에, 날짜는 노트북 셀 안에 박혀 있습니다.

하나 바꾸려면 코드를 뒤져야 합니다.

바뀔 수 있는 값을 config.json 한 파일로 모읍니다.

코드는 그대로 두고 이 파일만 바꿉니다.

이미 한 번 해 봤습니다 — API 키를 .env로 뺀 것입니다.

비밀은 .env, 비밀이 아닌 설정은 config.json에 둡니다.


2.1 바뀌는 값은 config.json에, 읽는 법은 json.load

with open("config.json", encoding="utf-8") as f:
    config = json.load(f)                 # 읽으면 딕셔너리가 된다

print(config["model"])                    # gemini-3.5-flash-lite

키값무엇을 정하나

model gemini-3.5-flash-lite 어느 LLM을 부르나
approve_severity high 어느 위험도부터 사람에게 확인받나 — 값이 아니라 정책이다
report_folder reports 보고서를 어느 폴더에 모으나
webhook_url http://127.0.0.1:5001/alert 알림을 어디로 보내나

파일담는 것깃허브에

.env 비밀 — API 키 올리지 않는다
config.json 설정 — 누가 언제 바꿨는지 남아야 하는 값 올린다

🐍 문법 상자 · 파이썬 딕셔너리와 JSON 파일은 조금 다릅니다

import json

print(json.dumps({"on": True, "url": None}))

# {"on": true, "url": null}

파이썬JSON 파일차이

True · False true · false 소문자
None null 이름이 다르다
'작은따옴표'도 된다 "큰따옴표"만 된다 json.dump가 알아서 바꾼다

⚠ JSON 파일에는 주석(#)을 쓸 수 없습니다.

설명은 코드나 노트북에 둡니다.


🐍 문법 상자 · in은 딕셔너리의 키를 봅니다

config = {"model": "gemini-3.5-flash-lite"}

print(
    "model" in config,
    "webhook_url" in config,
    "webhook_url" not in config
)

# True False True

쓰는 것결과뜻

"model" in config True 그 키가 있다
"webhook_url" not in config True 그 키가 없다
반복 안의 return 그 자리에서 함수가 끝난다 빠진 키 하나를 찾으면 더 보지 않는다

⚠ "gemini-3.5-flash-lite" in config는 False입니다.

in은 값이 아니라 키를 봅니다.


🐍 문법 상자 · 위험도는 숫자로 바꿔 비교합니다

RANK = {
    "low": 1,
    "medium": 2,
    "high": 3
}

print(RANK["high"] >= RANK["medium"])
print("high" >= "medium")

# True
# False

비교결과이유

RANK["high"] >= RANK["medium"] True 3 ≥ 2
"high" >= "medium" False 글자끼리는 사전 순서로 비교한다. h가 m보다 앞이다

⚠ 글자를 그대로 비교하면 뜻과 상관없는 결과가 나옵니다.

순위를 사전(RANK)으로 숫자로 바꾼 뒤 비교합니다.


한눈에

하려는 일쓰는 코드

설정을 저장한다 json.dump(config, f, ensure_ascii=False, indent=2)
설정을 읽는다 config = json.load(f) → config["model"]
빠진 키를 잡는다 if key not in config: return None
위험도를 비교한다 RANK[severity.lower()] >= RANK[config["approve_severity"]]

값은 파일에, 로직은 코드에 둡니다.


알림 연동 — 실패해도 멈추지 않게

웹훅 알림

웹훅(Webhook)은 특정 이벤트가 발생했을 때 HTTP POST 요청을 보내거나, 외부 서비스로부터 데이터를 받아 처리하는 방식.

파이썬의 requests 라이브러리를 사용해 디스코드나 슬랙 같은 플랫폼으로 웹훅 알림을 보내는 방법.

웹훅을 통해 슬랙이나 팀즈 같은 메신저에 알림을 보내는 방식은 HTTP POST 요청을 이용해 특정 웹훅 URL로 JSON 데이터를 전송하는 구조.

장애 허용

코드나 시스템의 일부에 오류(Exception, Crash, 네트워크 단절 등)가 발생하더라도, 전체 프로그램이 멈추지 않고 계속해서 동작하거나 안전하게 복구되도록 만드는 설계 방식.

한 부분의 에러나 장애가 전체 시스템의 중단으로 이어지지 않도록 하려면 장애 허용 아키텍처를 구축해야 한다.


왜 필요한가

보고서가 만들어져도 아무도 모르면 소용없습니다.

끝나면 담당자에게 알림을 보냅니다.

알림은 10/2에 만든 웹훅 서버로 보냅니다.

실무에서는 같은 자리에 슬랙 · 팀즈 · 메일 주소가 들어갑니다.

주소는 config.json에서만 바꾸면 됩니다.

알림 서버가 꺼져 있을 수 있습니다.

알림 하나 실패했다고 보고서까지 날아가면 안 됩니다.

9/28에 배운 try / except로 버팁니다.


3.1 알림 서버를 켜 둡니다

아래 셀을 실행해 alert_server.py를 만듭니다.

왼쪽 목록에서 agent_core 폴더를 오른쪽 클릭 › Open in Integrated Terminal을 누릅니다.

터미널에 입력합니다.

python alert_server.py

Running on http://127.0.0.1:5001이 보이면 그 터미널은 그대로 둡니다.

알림이 오면 여기에 [알림 받음]이 찍힙니다.

끌 때는 Ctrl + C입니다.

이런 글이 보이면이유할 일

Address already in use 10/2에 켠 서버가 아직 5001번을 쓰고 있다 그 터미널을 찾아 Ctrl + C
No module named 'flask' 터미널에 가상환경이 켜지지 않았다 터미널을 닫고 2번부터 다시
%%writefile alert_server.py

from flask import Flask, request                              # 10/2 에 쓴 웹 서버 도구

app = Flask(__name__)                                         # 서버를 하나 만든다


@app.route("/alert", methods=["POST"])                        # /alert 로 POST 가 오면 아래 함수가 받는다
def alert():                                                  # 알림을 받는 함수
    data = request.get_json()                                 # 보낸 JSON 을 딕셔너리로 꺼낸다
    print("[알림 받음]", data["text"])                         # 터미널에 알림 내용을 보여 준다
    return {"ok": True}                                       # 받았다고 답한다 (상태 코드 200)


app.run(port=5001)                                            # 5001 번 포트에서 기다린다

🐍 문법 상자 · requests.post의 json=과 timeout=

response = requests.post(
    url,
    json={"text": "알림"},
    timeout=5
)

print(response.status_code)          # 200 — 받았다는 뜻

쓰는 것뜻

url 보낼 주소. 오늘은 config["webhook_url"]에서 꺼낸다
json={"text": …} 딕셔너리를 JSON으로 바꿔 요청 몸통에 담는다
timeout=5 5초 안에 답이 없으면 기다리지 않고 에러를 낸다

⚠ timeout이 없으면 서버가 답을 주지 않을 때 셀이 끝나지 않고 멈춰 있습니다.


🐍 문법 상자 · except Exception as e — 에러를 잡아 이름 보기

try:
    int("abc")

except Exception as e:
    print("잡음:", type(e).__name__)

# 잡음: ValueError

쓰는 것잡는 것

except ValueError: 그 에러 하나만
except Exception as e: 거의 모든 에러. 잡은 에러가 e에 담긴다
type(e).__name__ 에러의 이름 — ValueError · ConnectionError

⚠ 에러를 잡고 아무것도 안 하면 에러가 났다는 사실이 드러나지 않습니다.

[알림 실패]처럼 반드시 출력하거나 기록합니다.


🐍 문법 상자 · try 안의 return

def safe_int(text):
    try:
        return int(text)

    except ValueError:
        return None


print(safe_int("5"), safe_int("abc"))

# 5 None

들어온 값실행되는 곳리턴하는 값

"5" try 안의 return 5
"abc" 에러가 나서 except 안의 return None

⚠ 실패해도 함수가 값을 리턴하므로, 부른 쪽은 멈추지 않고 다음 줄로 갑니다.


🐍 문법 상자 · "a" 모드는 뒤에 이어 씁니다

with open("alert_failures.log", "a", encoding="utf-8") as f:
    f.write("[보고서] … 저장\n")

모드파일이 있으면오늘 쓰는 곳

"w" 지우고 새로 쓴다 config.json · 보고서
"a" 뒤에 이어 쓴다 실패 기록 — 쌓여야 한다

⚠ "a"로 연 파일은 셀을 다시 실행할 때마다 줄이 늘어납니다.

줄 수가 2보다 많아도 맞습니다.


한눈에

하려는 일쓰는 코드

알림을 보낸다 requests.post(url, json={"text": message}, timeout=5)
실패를 잡는다 try: … except Exception as e:
무엇이 실패했나 type(e).__name__
실패해도 이어 간다 return False — 부른 쪽은 다음 줄로 간다

알림 하나가 실패해도 보고서는 남습니다.

실패는 숨기지 않고 출력합니다.


하나로 잇기 — pipeline.py

오케스트레이션

단순히 개별 작업을 자동화하는 것을 넘어, 여러 작업과 시스템의 실행 순서, 종속성, 조건문, 오류 복구 및 전체 워크플로 상태를 유기적으로 조율하고 관리하는 것.

엔트리 포인트

스크립트나 프로그램이 실행될 때 가장 먼저 코드가 시작되는 지점.

해당 파일의 첫 번째 줄(위쪽)부터 순서대로(위에서 아래로) 코드가 실행.


왜 필요한가

지금은 보고서 하나를 만들려면 노트북 셀을 순서대로 여러 개 눌러야 합니다.

하나 빠뜨리면 NameError가 납니다.

단계를 함수로 묶고, 함수 하나(run_pipeline)가 순서대로 부르게 합니다.

이것이 오케스트레이션입니다.

마지막에 pipeline.py 파일로 만들면 노트북 없이 터미널 명령 한 번으로 돕니다.

10/2에 배운 스케줄러로 매일 아침 돌릴 수도 있습니다.


4.1 run_pipeline이 단계를 차례로 부릅니다

run_pipeline("config.json", today)
  ├─ notifier.load_config        설정 읽기 · 검사
  ├─ run_report(events, today)   요약 → 정렬 → 총평 → 저장
  ├─ needs_approval 로 세기       사람 확인이 필요한 건수
  └─ notifier.notify             알림 · 실패해도 계속

단계넘겨받는 것넘겨주는 것

load_config 파일 이름 config 딕셔너리
run_report 경보 · 날짜 {"filename": …, "summaries": …}
notify 메시지 · 주소 True / False

앞 단계가 return한 값이 다음 단계의 입력입니다.


🐍 문법 상자 · print는 화면에, return은 다음 함수에

def show():
    print("보고서 저장")


def give():
    return "보고서 저장"


x = show()
y = give()

print(x, y)

# 보고서 저장
# None 보고서 저장

함수화면리턴하는 값

show() 보고서 저장 None — 아무것도 넘기지 않는다
give() 없음 "보고서 저장" — 변수에 담아 다음 함수에 넘길 수 있다

⚠ 함수 사이를 잇는 것은 return입니다.

print만 하면 다음 함수는 None을 받습니다.


🐍 문법 상자 · 값 여러 개는 딕셔너리 하나로 리턴합니다

def run():
    return {
        "filename": "daily_report_20261008.md",
        "count": 6
    }


result = run()

print(
    result["filename"],
    result["count"]
)

# daily_report_20261008.md 6

쓰는 것뜻

return {"filename": …, "summaries": …} 결과 두 개를 이름표를 붙여 한 번에 리턴한다
result["filename"] 받은 쪽에서 이름으로 꺼낸다

⚠ return은 한 번만 실행됩니다.

두 줄로 return a · return b를 쓰면 두 번째 줄은 실행되지 않습니다.


🐍 문법 상자 · if __name__ == "__main__": — 직접 실행할 때만

def run_pipeline(config_path, today):
    ...


if __name__ == "__main__":
    run_pipeline(
        "config.json",
        str(date.today())
    )

어떻게 쓰나__name__의 값아래 줄

터미널에서 python pipeline.py "__main__" 실행된다
노트북에서 import pipeline "pipeline" 실행되지 않는다 — 함수만 불러온다
str(date.today()) "2026-10-08" 꼴의 오늘 날짜  

⚠ 이 줄이 없으면 import pipeline만 해도 LLM을 두 번 부르고 알림까지 보냅니다.


🐍 문법 상자 · 폴더 만들기와 경로 잇기

os.makedirs(
    "reports",
    exist_ok=True
)

print(
    os.path.join(
        "reports",
        "daily_report_20261008.md"
    )
)

# Windows: reports\daily_report_20261008.md
# Mac: reports/daily_report_20261008.md

쓰는 것하는 일

os.makedirs(폴더, exist_ok=True) 폴더를 만든다. 있으면 그냥 지나간다
os.path.join(폴더, 파일) 그 컴퓨터에 맞는 구분 기호로 경로를 잇는다
shutil.copy(원본, 새 경로) 파일을 복사한다

⚠ "reports/" + filename처럼 직접 이으면 Windows와 Mac에서 모양이 달라집니다.

os.path.join을 씁니다.


한눈에

단계함수파일

설정 읽기 · 검사 load_config notifier.py
요약 → 정렬 → 총평 → 저장 run_report pipeline.py
확인 건수 세기 needs_approval notifier.py
알림 notify notifier.py
전체 실행 순서 run_pipeline pipeline.py

오전 산출물은 agent_core/config.json · notifier.py · pipeline.py입니다.

오후에는 이 코드를 리뷰하고, 테스트하고, 에러를 고칩니다.


코드 리뷰와 단위 테스트

코드 리뷰

다른 개발자가 작성한 코드를 체계적으로 검토하여 버그를 찾고, 코드 품질을 높이며, 유지보수성을 강화하는 과정.

다른 사람의 파이썬 코드를 리뷰할 때는 전체적인 구조(작동 여부)에서 시작해 점차 세부적인 스타일과 효율성으로 좁혀 나가는 것.

단위 테스트

코드의 가장 작은 단위(함수나 클래스 등)가 의도한 대로 정확히 동작하는지 자동으로 검증하는 방법.

파이썬의 내장 모듈인 unittest를 사용하면 함수가 올바르게 작동하는지 자동으로 검증할 수 있다.


왜 필요한가

코드가 돌아간다고 맞는 것은 아닙니다.

키가 코드에 적혀 있거나, 실패를 숨기는 코드도 잘 돕니다.

코드 리뷰 — 사람이 체크리스트로 한 줄씩 읽습니다.

매번 같은 항목을 보니 빠뜨리지 않습니다.

단위 테스트 — assert로 「이 입력이면 이 값이 나와야 한다」를 적어 둡니다.

코드를 고친 뒤 한 번 돌리면 망가진 곳을 바로 압니다.


5.1 리뷰 체크리스트 — 이 코드는 다섯 항목 가운데 어디를 어기나

from llm_client import read_api_key, parse_llm_json

api_key = read_api_key()                                                # (가)

body = {
    "model": "gemini-3.5-flash-lite",
    "messages": messages
}                                                                      # (나)

response = requests.post(
    URL,
    headers=headers,
    json=body
)                                                                      # (다)

judgment = parse_llm_json(
    response.json()["choices"][0]["message"]["content"]
)                                                                      # (라)

try:
    requests.post(
        "http://127.0.0.1:5001/alert",
        json={"text": "완료"},
        timeout=5
    )

except Exception:
    pass                                                                # (마)

항목확인할 것볼 줄

비밀 API 키가 코드에 직접 적혀 있지 않은가 (가)
설정 모델 이름 · 주소가 config.json에서 오는가 (나)
검증 LLM 답을 그대로 믿지 않고 확인하는가 (라)
실패 요청에 timeout이 있는가 (다)
기록 실패를 숨기지 않고 알리는가 (마)

🐍 문법 상자 · assert — 참이면 조용히, 거짓이면 멈춘다

assert 1 + 1 == 2, "1 + 1 은 2 여야 한다"

print("첫 번째 검사 통과")

assert 1 + 1 == 3, "1 + 1 은 2 여야 한다"

print("두 번째 검사 통과")

결과:

첫 번째 검사 통과
AssertionError: 1 + 1 은 2 여야 한다

쓰는 것결과

assert 조건, "메시지" 조건이 참이면 통과, 거짓이면 AssertionError: 메시지로 멈춘다
assert 함수(입력) == 기대값 함수가 기대한 값을 리턴하는지 확인한다 — 이것이 테스트다

⚠ 메시지를 빼면 AssertionError만 보여서 무엇이 틀렸는지 모릅니다.

메시지에 「무엇이 어때야 하는지」를 씁니다.


🐍 문법 상자 · None인지는 is None으로 봅니다

result = None
empty = ""

print(
    result is None,
    empty is None,
    result == empty
)

# True False False

값is None뜻

None True 값이 없다 — parse_llm_json이 실패하면 리턴한다
"" False 빈 문자열 — call_llm이 실패하면 리턴한다

⚠ None과 ""는 다른 값입니다.

어느 함수가 무엇을 리턴하는지 보고 비교합니다.


한눈에

방법하는 일쓰는 것

코드 리뷰 매번 같은 항목으로 코드를 읽는다 비밀 · 설정 · 검증 · 실패 · 기록
단위 테스트 입력과 기대값을 적어 두고 자동으로 확인한다 assert 함수(입력) == 기대값, "메시지"
테스트 파일 고칠 때마다 한 번에 확인한다 python test_agent_core.py

디버깅 — 에러가 있는 코드 고치기

디버깅

코드에 숨겨진 오류(버그)를 찾아내고 수정하는 과정.

  • TraceBack 읽기
  • 가설 설정 및 문제 범위 좁히기
  • 중간값 확인하기(Print 디버깅)

트레이스백

프로그램 실행 중 예외(오류)가 발생했을 때, 오류가 발생하기까지 함수들이 호출된 경로(스택)를 역추적하여 보여주는 보고서.

트레이스백(Traceback) 에러 메시지는 아래에서 위로 읽어야됨.


왜 필요한가

지금까지 에러가 나면 정답을 봤습니다.

캡스톤에서는 정답이 없습니다.

원인은 에러 메시지에 적혀 있습니다.

에러는 세 종류입니다.

  • 문법 에러 — 실행 전에 막힌다
  • 실행 에러 — 실행 중에 멈춘다
  • 논리 에러 — 멈추지 않고 틀린 결과를 낸다

6교시 문제는 모두 에러가 있는 코드로 시작합니다.

먼저 그대로 실행해 무엇이 보이는지 읽고, 고칩니다.


6.1 에러가 나면 마지막 줄부터 읽습니다

순서할 일

1 마지막 줄의 에러 이름과 내용을 읽는다 — KeyError: 'sevrity'
2 그 위의 줄 번호로 가서 그 줄을 본다
3 에러가 없는데 결과가 이상하면 의심 가는 줄 위에 print("[디버그]", 값)을 넣는다
4 고친 뒤 다시 실행해 기대한 결과와 비교한다

🐍 문법 상자 · 에러의 세 종류

def make_title(today)          # 문법 에러 (syntax error) — 한 줄도 실행되지 않는다

print(judgment["sevrity"])     # 실행 에러 (runtime error) — 위 줄까지는 돌고 여기서 멈춘다

if level == "high":            # 논리 에러 (logic error) — 에러 없이 틀린 결과가 나온다

종류에러 메시지찾는 법

문법 에러 (syntax error) SyntaxError — 실행 전에 나온다 ^ 표시가 가리키는 자리를 본다
실행 에러 (runtime error) KeyError · NameError 등 마지막 줄의 에러 이름과 줄 번호를 본다
논리 에러 (logic error) 없음 print로 실제 값을 찍어 기대와 비교한다

⚠ 세 번째가 가장 위험합니다.

에러가 없어서 틀린 보고서가 그대로 나갑니다.

그래서 5교시에 테스트를 붙였습니다.


🐍 문법 상자 · 에러 메시지는 아래에서 위로 읽습니다

함수 안에서 에러가 나면 트레이스백(Traceback · 트레이스백 · 에러가 난 길)이 두 덩어리로 나옵니다.

KeyError                                  Traceback (most recent call last)

Cell In[1], line 8
      7 for j in judgments:
----> 8     lines = lines + make_line(j) + "\n"

Cell In[1], line 2, in make_line(j)
      1 def make_line(j):
----> 2     return f"- [{j['sevrity']}] {j['rule']} → {j['tool']}"

KeyError: 'sevrity'

읽는 순서보는 것위 예에서

③ 맨 아래 줄 에러 이름과 문제가 된 값 KeyError: 'sevrity' — 그런 키가 없다
② 바로 위 덩어리 실제로 멈춘 줄 — 대개 여기를 고친다 make_line의 2번째 줄
① 맨 위 덩어리 그 함수를 부른 줄 반복문의 8번째 줄 — 여기는 잘못이 없다

마지막 줄뜻먼저 볼 곳

KeyError: 'sevrity' 딕셔너리에 그 키가 없다 키 이름의 철자
NameError: name 'x' is not defined 그 이름을 만든 적이 없다 그 줄이 있는 함수 안에서 만들었거나 받았는지
SyntaxError: expected ':' 문법이 틀렸다 ^가 가리키는 자리

⚠ ----> 8의 화살표가 그 덩어리에서 멈춘 줄입니다.

Cell In[1], line 8은 셀 안의 8번째 줄이라는 뜻입니다.


🐍 문법 상자 · print로 실제 값 찍어 보기

level = " high"

print("[디버그]", level)

print(f"[디버그] [{level}]")

결과:

[디버그]  high
[디버그] [ high]

할 일쓰는 것

의심 가는 줄 바로 위에 값을 찍는다 print(f"[디버그] [{변수}]")
기대한 값과 글자 하나씩 비교한다 [high]를 기대했는데 [High] · [ high]가 찍혔다
고친 뒤 assert로 다시 생기지 않게 막는다 assert high_count == 4, "high 건수가 다르다"

⚠ 대소문자는 .lower(), 앞뒤 공백은 .strip()으로 맞춥니다.

둘을 이어 쓸 수 있습니다.

값.strip().lower()

한눈에

종류보이는 것오늘 고친 것

문법 에러 SyntaxError — 한 줄도 안 돈다. 하나를 고치면 다음 것이 나온다 def 끝의 : · 닫지 않은 괄호
실행 에러 KeyError · NameError — 트레이스백이 두 덩어리로, 아래 덩어리가 멈춘 줄 함수 안의 키 철자 · 함수가 받지 않은 이름
논리 에러 에러 없이 틀린 값 .strip().lower() · >= · 정렬 순서 · \n

고친 뒤에는 5교시처럼 assert를 붙여 같은 에러가 다시 생기면 바로 멈추게 합니다.


완성과 회고

회고

과거의 경험을 돌아보고 더 나은 방향으로 나아갈 방법을 찾는 과정.

개발 팀이 회고(Retrospective)를 진행할 때 작성하는 내용은 단순히 ‘무엇을 했다’는 보고가 아니라, ‘우리 팀이 어떻게 하면 더 잘 일할 수 있을까?’에 대한 논의와 액션 플랜이다.

장애 허용

코드나 서버 일부에 에러나 중단이 발생해도, 전체 프로그램이 멈추지 않고 계속 실행되거나 안전하게 복구되도록 만드는 설계 방식.

장애 허용(Fault Tolerance) 아키텍처를 설계해야한다.


왜 필요한가

1과목의 마지막 시간입니다.

만든 것을 처음부터 끝까지 한 번 더 돌려 봅니다 — 정상일 때, 알림 서버가 꺼졌을 때.

8일 동안 무엇이 어려웠는지는 오늘이 아니면 잊습니다.

캡스톤에서 막힐 때 다시 열어 볼 기록을 남깁니다.

마지막으로 오늘의 산출물을 깃허브에 올립니다.


7.1 알림 서버를 켜고 시작합니다

오전 3.1처럼 agent_core 폴더를 오른쪽 클릭 › Open in Integrated Terminal을 누릅니다.

터미널에 입력합니다.

python alert_server.py

그 터미널은 그대로 둡니다.

오전에 켠 서버가 아직 떠 있으면 Address already in use가 나옵니다.

그때는 켜진 그대로 쓰면 됩니다.

오늘 확인할 것문제

명령 한 번으로 보고서 → 알림까지 간다 7-2
알림 서버가 꺼져도 보고서는 남는다 7-3
설정만 바꾸면 코드를 고치지 않아도 동작이 바뀐다 ⭐7-6

🐍 문법 상자 · %%writefile로 다른 폴더에 저장하기

%%writefile ../docs/day08_retrospective.md

# 1과목 회고 (이름)

경로뜻

day08_retrospective.md 지금 폴더(agent_core) 안
../docs/day08_retrospective.md 한 칸 위(security-agent-toolkit)의 docs 폴더 안

⚠ docs 폴더가 없으면 저장되지 않습니다.

바로 위 셀이 폴더를 먼저 만듭니다.


한눈에

확인한 것어떻게

명령 한 번으로 끝까지 pipeline.run_pipeline("config.json", 날짜)
알림이 실패해도 보고서는 남는다 config_off.json — 주소만 바꿨다
설정이 동작을 바꾼다 config_low.json — 기준만 바꿨다
기록 docs/day08_retrospective.md

1과목에서 만든 것

파일하는 일만든 날

llm_client.py LLM을 부르고 답을 JSON으로 읽는다 10/6
event_summarizer.py 경보를 묶음으로 요약하고 위험도순으로 정렬한다 10/7
report_generator.py 총평을 받고 보고서를 만든다 10/7
config.json 모델 · 정책 · 폴더 · 알림 주소 10/8
notifier.py 설정을 검사하며 읽고, 알림을 보낸다. 실패해도 멈추지 않는다 10/8
pipeline.py 위 모두를 명령 한 번으로 잇는다 10/8
test_agent_core.py 고칠 때마다 망가진 곳이 없는지 본다 10/8

 

 

와 드디어 AI자동화 기초가 끝났습니다..

다음주부터는 네트워크를 들어가게 됐는데 설레구,,내일부터 쉬는날이라 기쁘네요

그럼 아됴

+ Recent posts