# Step 095 NetWatch 실습

자신이 소유하거나 명시적으로 점검을 허가받은 랩에서만 사용하세요. 이 안내와 자동 재현은 `127.0.0.1`만 사용합니다. Python 3.10 이상과 표준 라이브러리가 필요하며 별도 패키지를 설치하지 않습니다.

## 시작

압축을 풀고 해당 폴더에서 실행합니다. Linux에서는 아래 `python`을 `python3`으로 바꿉니다.

```text
python netwatch.py --help
python reproduce.py
```

`reproduce.py`는 임시 루프백 서버를 만들고 실제 CLI와 DB를 검증한 뒤 서버와 시험 DB를 정리합니다. 기본값은 빈 임시 포트입니다. `--fixed-ports`는 8000·8001·9000이 비어 있는 격리 환경에서만 사용합니다.

## 직접 확인하는 세 단계

터미널 1, 2에서 각각 다음 서버를 켭니다.

```text
python -m http.server 8000 --bind 127.0.0.1
python -m http.server 9000 --bind 127.0.0.1
```

터미널 3에서 아래 명령을 두 번 실행합니다. 첫 실행은 BASELINE, 두 번째는 UNCHANGED여야 합니다. 최초부터 8001을 검사 목록에 포함합니다.

```text
python netwatch.py 127.0.0.1 127.0.0.1 --ports 8000,8001,9000 --db practice.sqlite3
```

터미널 4에서 8001 서버를 켜고 같은 검사 명령을 다시 실행하면 `[!] NEW: 127.0.0.1:8001`을 확인할 수 있습니다.

```text
python -m http.server 8001 --bind 127.0.0.1
```

8001 서버를 Ctrl+C로 종료하고 다시 검사하면 `NOT OBSERVED`가 나와야 합니다. 이 상태는 이번에 연결하지 못했다는 뜻이며 방화벽 차단이나 지연 등과 영구 폐쇄를 구분하지 않습니다. 실습을 마치면 나머지 서버도 Ctrl+C로 종료합니다.

## 입력과 결과의 의미

- 시작·끝은 같은 /24의 오름차순 IPv4 주소입니다. 서로 다른 /24, 역순, 잘못된 주소는 거부합니다. 전체 /24는 실제 네트워크 설정을 추론한 결과가 아니라 이 교육용 구현이 정한 최대 범위입니다.
- `--ports`는 1~65535 사이의 쉼표 구분 정수이며 최대 64개입니다. 기본 목록은 `22,80,443,445,3389,8000,8001,9000`입니다.
- `--timeout`은 각 연결/배너 대기의 제한이며 기본 0.3초, 허용 0.05~2초입니다. IP 수×포트 수×대기 시간이 커지면 전체 실행도 길어집니다.
- 응답 IP 수는 서로 다른 IP 주소 수이며 물리적 기기 수를 알아낸 값이 아닙니다. TCP 연결 성공은 그 시점에 관찰된 상태입니다.
- 배너는 최대 64바이트만 기다립니다. HTTP처럼 요청 전까지 먼저 말하지 않는 서비스는 빈 배너가 정상일 수 있습니다. 배너 수신 실패나 연결 후 RST도 이미 성공한 연결 관찰을 지우지 않습니다.
- 같은 주소 범위·포트 목록·시간 제한으로 끝까지 실행된 직전 검사와만 비교합니다. 조건을 바꾸면 별도 BASELINE입니다. 포트 목록의 입력 순서나 중복은 정규화합니다.
- 기본 DB는 `netwatch-lab.sqlite3`입니다. 과거 예제의 `netwatch.db`를 덮어쓰지 않습니다. 다른 스키마의 파일은 거부하며 자동 변환·삭제하지 않습니다. 처음부터 연습하려면 새로운 `--db` 파일명을 사용합니다.
- 종료 코드: 성공 0, 입력 오류 2, 실행/DB 오류 1, Ctrl+C 130. 중단 시 마지막으로 커밋된 검사까지 남을 수 있으므로 DB를 확인합니다. 한 검사의 헤더와 결과는 함께 저장되거나 함께 취소됩니다.

## DB 살펴보기

Python 3.12 이상에서는 별도 sqlite3 프로그램 없이 조회할 수 있습니다.

```text
python -m sqlite3 practice.sqlite3 "SELECT id,scan_time FROM scans ORDER BY id;"
python -m sqlite3 practice.sqlite3 "SELECT scan_id,ip,port,banner FROM findings ORDER BY scan_id,port;"
```

3.10·3.11에서는 Python 대화형에서 `sqlite3.connect()`와 `execute()`를 사용하세요. `conn.execute(...)`도 커서를 반환하므로 `cur = conn.execute(...); scan_id = cur.lastrowid`가 가능합니다. 데이터 값은 `?` 매개변수로 전달합니다.

## 복습

1. 8001을 첫 검사 포트 목록에서 빼면 왜 세 번째 검사에 NEW가 아닌 BASELINE이 나오는가?
2. 빈 배너와 연결 실패를 구분해야 하는 이유는 무엇인가?
3. IP 주소 범위는 같은데 타임아웃을 바꾸면 어떤 기준선을 써야 하는가?
4. 여러 검사가 저장된 뒤 과거 scan_id를 조회했을 때 최신 두 검사를 비교하면 왜 잘못되는가?

해답: 1은 검사 조건이 달라졌기 때문, 2는 배너를 기다리는 서비스도 TCP 연결은 성공할 수 있기 때문, 3은 새 조건의 별도 기준선, 4는 요청한 시점과 비교 시점이 달라지기 때문입니다.

공식 참고: https://docs.python.org/3/library/socket.html · https://docs.python.org/3/library/sqlite3.html · https://docs.python.org/3/library/ipaddress.html
