Devin.KR

주소 중복과 대역 겹침

90분 안팎

학습 목표

학교 주소표를 읽어 중복 주소·대역 겹침·대역 밖 게이트웨이를 판정합니다.

개념

주소표는 변경할 설정의 기준입니다

학생 단말이 번갈아 끊기면 단말의 IP 중복을 의심할 수 있지만 추측만으로 주소를 바꾸면 인계 문서와 설정이 어긋납니다. 학교 프로젝트는 역할별 대역과 단말별 주소를 한 표에 기록하고 검사한 뒤 다음 설정에 반영합니다. 이 레슨에서는 개별 주소가 유효한지뿐 아니라 여러 행이 서로 모순되지 않는지 검사합니다. 주소 계산기를 통과한 값도 다른 단말과 중복될 수 있습니다.

이번 미션의 상위 공간은 10.20.0.0/16입니다. 학생 10.20.10.0/24, 교직원 10.20.20.0/24, 서버 10.20.30.0/24, 게스트 10.20.40.0/24를 예시 설계로 사용합니다. 각 역할에 별도 /24를 주고 .1은 게이트웨이, .11 이후는 현재 단말로 예약합니다. 학생 s1은 .11, s2는 .12입니다. 네 대역은 실제 학교 자료를 복제한 값이 아니라 이 프로젝트에서 합의한 가상 주소입니다.

주소표의 열은 id, role, address, mask, gateway입니다. ID는 주소가 바뀌어도 유지하고 role은 사용 역할, address는 단말 IPv4, mask는 점표기 마스크, gateway는 같은 망 라우터 인터페이스입니다. 라우터 r1 자체는 네 개의 망에 각각 인터페이스 주소를 가지므로 하나의 단말 행으로 억지로 합치지 않고 topology의 게이트웨이 주소와 연결합니다. 이후 VLAN 번호는 역할 대역에 대응시키되 지금 주소표에 구현됐다고 표시하지 않습니다.

중복 주소와 대역 겹침은 다른 오류입니다

s1과 s2가 둘 다 10.20.10.11이면 같은 학교 실습 공간에서 주소 충돌입니다. 두 장비 ID가 다르더라도 주소 값은 같으므로 집합에 넣기 전에 이미 있는지 검사합니다. 반대로 학생이 .11이고 교직원이 .21이면 단말 주소 자체는 다르지만 둘의 역할 망이 모두 10.20.10.0/24라면 역할별 분리 설계가 깨집니다. 이를 대역 겹침으로 별도 보고해야 어떤 설정을 바꿀지 명확해집니다.

학생 단말 둘이 같은 /24에 속하는 것은 정상입니다. 모든 행의 망을 서로 비교해서 같으면 무조건 오류라고 하면 학생 두 대를 한 망에 둘 수 없습니다. 먼저 역할별 망 선언을 모읍니다. 같은 역할은 동일 망과 동일 게이트웨이를 공유해야 하고, 서로 다른 역할은 overlaps로 비교합니다. 브라우저 실습에서는 같은 역할의 서로 다른 망 선언을 ERROR로 보고 역할 설계부터 고치게 합니다.

대역 시작 주소가 다르다는 사실만으로 겹치지 않는 것은 아닙니다. 10.20.10.0/24와 10.20.10.128/25는 시작이 다르지만 두 번째 전체가 첫 번째 안에 들어갑니다. 두 CIDR을 문자열로 비교하지 않고 범위의 교집합을 검사합니다. 반면 10.20.10.0/25와 10.20.10.128/25는 끝과 시작이 맞닿아도 주소를 공유하지 않으므로 겹치지 않습니다. 경계의 포함 규칙이 검사 결과를 바꿉니다.

게이트웨이는 주소표 안에서 예약합니다

게이트웨이가 대역 밖에 있으면 단말이 일반적인 연결 경로로 그 주소를 ARP할 수 없습니다. 10.20.10.11/24에 10.20.20.1을 기본 게이트웨이로 적는 것이 예입니다. 같은 역할의 다른 단말도 동일 게이트웨이를 사용하도록 검사하고, 망 시작·브로드캐스트·단말 자신의 주소도 거부합니다. 특수한 onlink 구성은 이번 설계 계약에서 사용하지 않습니다. 예외 설정으로 표의 오류를 감추지 않습니다.

학생 s1에 .11, s2에 .1을 배정하고 게이트웨이가 .1이면 단말과 라우터 주소가 충돌합니다. 게이트웨이가 해당 행 주소와 다르다는 검사만으로는 다른 행의 충돌을 놓칠 수 있습니다. 모든 단말 주소의 집합과 모든 게이트웨이 집합의 교집합도 확인합니다. “대역에 포함됨”은 필요한 조건이지만 단말 사용 가능·중복 없음·게이트웨이 예약의 여러 조건을 합쳐야 승인할 수 있습니다.

예약 범위를 문서로 남기는 이유는 현재 다섯 대만이 앞으로도 전부라는 보장이 없기 때문입니다. 지금 비어 있는 .2부터 .10까지를 어떤 용도로 보류했는지 설명하면 다음 담당자가 장비를 늘릴 때 충돌을 피할 수 있습니다. 이 미션의 자동 검사는 망 끝과 게이트웨이 예약을 확인하며 별도 조직의 DHCP 풀이나 장비 재고까지 알지는 못합니다. 자동 통과와 운영 주소 관리의 책임을 구분합니다.

검사 순서를 계약으로 정합니다

브라우저 입력은 헤더 없이 id,role,IPv4/프리픽스,gateway 네 칸을 가진 행입니다. 역할은 student·staff·server·guest이고 ID는 소문자로 시작하는 영숫자 1~10글자입니다. 공백과 빈 줄은 허용하지만 빈 목록은 ERROR입니다. 각 프리픽스는 /1부터 /30까지 지원합니다. 미션은 네 역할 /24만 사용하지만 작은 범위와 겹침 이해를 위해 브라우저는 /25 등도 계산합니다.

출력은 판정 한 줄입니다. 형식·ID·역할별 망 불일치가 있으면 ERROR, 단말 IP가 중복이면 DUPLICATE, 서로 다른 역할 망이 겹치면 OVERLAP, 게이트웨이가 부적절하면 GATEWAY, 단말이 망 시작이나 끝이면 RESERVED, 나머지는 OK입니다. 여러 결함이 동시에 있으면 이 우선순서에서 처음 해당하는 결과를 출력합니다. 진단을 결정적으로 만들기 위한 계약이며 앞 오류를 고친 뒤 다음 검사에 다시 들어갑니다.

구현은 모든 행을 먼저 파싱해 객체를 모으고 검사를 단계별로 수행합니다. 한 행을 읽자마자 OK를 출력하지 않습니다. 뒤에서 같은 주소가 다시 등장할 수 있기 때문입니다. 중복 ID와 IP 중복도 구분합니다. ID 중복은 목록 구조 오류인 ERROR이고 IP 중복은 주소 할당 문제인 DUPLICATE입니다. 어떤 입력을 고쳐야 하는지 판정 이름을 바탕으로 설명하는 연습을 합니다.

CSV와 구성도 사이의 불일치를 놓치지 않습니다

미션의 addresses.csv와 topology.json은 같은 장비를 두 방식으로 표현합니다. CSV에는 s2 주소가 .12인데 topology가 .11이면 표만 검사해서는 실제 구성의 충돌을 막을 수 없습니다. 제공 문서 검사기는 ID 참조, 단말 주소/24, 라우터 쪽 게이트웨이를 교차 비교합니다. 파일을 고친 뒤 python3 test_docs.py를 실행하고 Linux 적용은 별도 check.sh로 진행합니다. 앞 모듈의 requirements.csv는 변경하지 않습니다.

시작 묶음에는 학생 주소 중복을 하나 넣었습니다. 테스트를 통과시키기 위해 중복 검사를 삭제하는 대신 s2의 주소를 표와 설계에서 일치시킵니다. 검사가 기대한 이유로 실패하는지 오류 메시지의 address: duplicate 부분을 읽습니다. 표가 정상일 때도 시험용 복사본에 중복·예약·대역 밖 게이트웨이·잘못된 마스크를 넣어 거부되는지 확인합니다. 정답 파일에 오류를 주입하지 않고 임시 폴더에서 검사합니다.

브라우저 검사는 주소 관계를 연습하는 도구이며 역할별 통신 차단을 구현하지 않습니다. 주소가 나뉘었다고 게스트 웹이 자동으로 차단되지는 않습니다. 실제 VLAN과 라우팅·방화벽은 뒤의 모듈에서 확장합니다. 제출할 때 주소표의 승인 근거와 현재 적용 상태를 나누고 다음 담당자가 네 역할의 경계를 재현할 수 있게 적습니다. 반복문과 조건문의 일반 문법은 서재에서 보완하고 여기서는 검사 의미에 집중합니다.

따라하기

단말 중복을 집합으로 찾기

주소표의 한 검사를 떼어 실행하고 어떤 오류를 구별하는지 기록합니다.

addresses=['10.20.10.11','10.20.10.11']
print('unique',len(set(addresses)))
print('DUPLICATE' if len(set(addresses))!=len(addresses) else 'OK')

실행 결과

unique 1
DUPLICATE

포함과 인접한 범위 구분

주소표의 한 검사를 떼어 실행하고 어떤 오류를 구별하는지 기록합니다.

import ipaddress as ip
base=ip.ip_network('10.20.10.0/24')
left=ip.ip_network('10.20.10.0/25')
right=ip.ip_network('10.20.10.128/25')
print('inside',base.overlaps(right))
print('adjacent',left.overlaps(right))

실행 결과

inside True
adjacent False

게이트웨이 범위 확인

주소표의 한 검사를 떼어 실행하고 어떤 오류를 구별하는지 기록합니다.

import ipaddress as ip
a=ip.ip_interface('10.20.10.11/24')
for text in ['10.20.10.1','10.20.20.1','10.20.10.255']:
    g=ip.ip_address(text)
    usable=g in a.network and g not in (a.network.network_address,a.network.broadcast_address,a.ip)
    print(text,usable)

실행 결과

10.20.10.1 True
10.20.20.1 False
10.20.10.255 False

주소표의 네 역할 망 만들기

주소표의 한 검사를 떼어 실행하고 어떤 오류를 구별하는지 기록합니다.

import ipaddress as ip
for role,third in [('student',10),('staff',20),('server',30),('guest',40)]:
    n=ip.ip_network(f'10.20.{third}.0/24')
    print(role,n,'gateway',n.network_address+1)

실행 결과

student 10.20.10.0/24 gateway 10.20.10.1
staff 10.20.20.0/24 gateway 10.20.20.1
server 10.20.30.0/24 gateway 10.20.30.1
guest 10.20.40.0/24 gateway 10.20.40.1

확인 문제

실습

헤더 없는 id,role,IPv4/프리픽스,gateway 행을 검사합니다. 역할은 student/staff/server/guest, ID는 소문자로 시작하는 영숫자 1~10글자, 프리픽스는 /1~ /30입니다. 공백·빈 줄 허용, 빈 목록 거부. 같은 역할은 같은 망·게이트웨이를 씁니다. 출력은 우선순서대로 ERROR(형식·중복 ID·역할 선언 불일치), DUPLICATE(단말 IP), OVERLAP(다른 역할 망), GATEWAY(대역 밖·예약 끝·단말 주소 충돌), RESERVED(단말이 망 시작/끝), OK 중 한 줄입니다. 부분 목록도 허용합니다. 모든 입력을 읽고 판정합니다.

모범 답안
import ipaddress as ip
import re
import sys

def main():
    rows=[]; ids=set(); roles={}
    try:
        for raw in sys.stdin.read().splitlines():
            if not raw.strip(): continue
            cols=[c.strip() for c in raw.split(',')]
            if len(cols)!=4: raise ValueError()
            device,role,cidr,gw=cols
            if not re.fullmatch(r'[a-z][a-z0-9]{0,9}',device) or device in ids: raise ValueError()
            if role not in ('student','staff','server','guest'): raise ValueError()
            if not re.fullmatch(r'[0-9]+(?:\.[0-9]+){3}/(?:[1-9]|[12][0-9]|30)',cidr): raise ValueError()
            a=ip.IPv4Interface(cidr); g=ip.IPv4Address(gw);n=a.network
            if role in roles and roles[role]!=(n,g): raise ValueError()
            roles[role]=(n,g);ids.add(device);rows.append((a,g))
        if not rows: raise ValueError()
    except ValueError:
        print('ERROR');return
    addresses=[a.ip for a,g in rows]
    if len(set(addresses))!=len(addresses): print('DUPLICATE');return
    nets=[v[0] for v in roles.values()]
    if any(a.overlaps(b) for i,a in enumerate(nets) for b in nets[i+1:]):
        print('OVERLAP');return
    if any(g not in a.network or g in (a.network.network_address,a.network.broadcast_address) or g in addresses for a,g in rows):
        print('GATEWAY');return
    if any(a.ip in (a.network.network_address,a.network.broadcast_address) for a,g in rows):
        print('RESERVED');return
    print('OK')
main()

더 읽기

면접 질문

  • 같은 서브넷과 다른 서브넷으로 통신하는 흐름을 설명합니다.