본문 바로가기
프로그래밍

파이썬으로 특정 호스트의 포트 접근 가능 여부를 확인해보자

by choihyuunmin 2022. 3. 20.

개요

보안이 철저한 서버는 보통 폐쇄망으로 구성합니다. 다른 서버로부터의 네트워크 접근이나 다른 서버로 나가는 패킷을 철저하게 제한하기 때문에 네트워킹이 필요한 호스트를 따로 구분하여 방화벽 정책에 추가하는 식으로 운영합니다. 대상 서버의 아이피나 포트번호가 바뀌거나 삭제되는 일도 간간이 생기기 때문에 폐쇄망에서는 주기적으로 호스트와 포트의 오픈 여부를 체크해 줄 필요가 있습니다.


준비

우선 포트 오픈 여부 확인을 위해서는 대상 서버의 hostname, ip, port를 정리해야 합니다. 폐쇄망 환경상 파이썬 라이브러리 설치에 한계가 있기 때문에 내장 모듈을 통해 진행했습니다. 파이썬 기본 내장 모듈에는 엑셀을 읽을 수 있는 방법이 없기 때문에, 기존 방화벽 정책 관련 정리 문서를 엑셀(xlsx) 파일에서 CSV 파일로 변경했습니다.

hostname ip port
host1 1.1.1.1 80
host2 1.1.1.1 443
host3 10.10.10.10 8080
host4 10.10.10.10 9191

CSV 형식은 위와 같이 hostname, ip, port 형식으로 간단하게 구성했습니다.


구현

# port_check.py
#!/usr/bin/python3
import socket
import sys
import csv

가져온 모듈은 저수준 네트워크 인터페이스를 위한 socket 과 파이썬 실행 시 csv 파일을 인자로 받기 위한 sys 모듈, 그리고 csv 세 개뿐입니다. 앞서 설명했듯이, 폐쇄망에서는 파이썬 라이브러리 설치에 한계가 있기 때문에 기본 내장 모듈만 사용합니다.

 

HOSTNAME = socket.gethostname()

def open_csv_file(csv_file):
    with open(csv_file, 'r') as ex:
        next(ex)
        fileReader = csv.reader(ex)
        contents = list(fileReader)
        return contents

def check_socket(hostname: str, ip: str, port: str):
    global HOSTNAME
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    if hostname == HOSTNAME:
        res = sock.connect_ex((ip, int(port)))
        if res == 0:
            print(f"HOST : {ip} - PORT : {port} >>> CONNECTED")
        else:
            print(f"HOST : {ip} - PORT : {port} >>> NOT CONNECTED")
  
      sock.close()

두 개의 함수를 만들었는데, 하나는 csv 파일을 불러서 읽는 모듈, 하나는 포트의 오픈 여부를 확인하는 함수를 선언했습니다.

먼저, open_csv_file에서 csv_file 파라미터를 가져와 열어주고, next() 함수를 이용하여 첫 번째 줄인(hostname, ip, port)를 제외하고 두 번째 줄부터 불러왔습니다(hostname1, 1.1.1.1, 80). csv 파일을 불러와 리스트로 변환한 후 contents 변수에 담아서 반환시킵니다.

>> [['hostname1', '1.1.1.1', '80'], ['hostname2', '1.1.1.1', '443'], ['hostname3', '10.10.10.10', '8080'], ['hostname4', '10.10.10.10', '9191']]

 

check_socket에서는 socket을 먼저 생성해주는데 이때 AF_INET은 IPv4 프로토콜을, SOCK_STREAM은 연결 지향형 소켓, TCP 패킷을 사용하겠다라는 의미입니다. 참고로 비 연결 지향형(UDP 소켓)을 할당하기 위해선 SOCK_STREAM을 SOCK_DGRAM으로 변경해주면 됩니다.

socket을 생성했으니 생성된 소켓에 ip와 port를 입력하여 통신이 원활히 이루어지는지 확인해봅니다. socket에서는 connect_ex 라는 함수를 제공하는데 연결이 정상적으로 이루어지면 0을 반환하고, 그렇지 않으면 error를 반환합니다.

파이썬 스크립트가 실행되고 있는 hostname과 CSV 파일의 hostname이 일치하는 행의 ip와 port를 인자로 받아서 연결을 확인해봅니다. 결과값이 0이면 "CONNECT" 메시지를, 정상값 0이 아닌 다른 결과값이 반환되면 "NOT CONNECTED" 메시지를 출력해주고 socket을 닫습니다.

 

def main():
    contents = open_csv_file(sys.argv[1])
    for content in contents:
        check_socket(content[0], content[1], content[2])

메인함수입니다. 처음 만들었던 open_csv_file 함수에 실행인자를 넣어 리스트로 반환하고 한줄씩 check_socket에서 돌려줍니다. 

>> HOST : 1.1.1.1 - PORT : 80 >>> CONNECTED
>> HOST : 1.1.1.1 - PORT : 443 >>> NOT CONNECTED
>> HOST : 10.10.10.10 - PORT : 8080 >>> CONNECTED
>> HOST : 10.10.10.10 - PORT : 9191 >>> CONNECTED

코드가 돌아가는 서버(hostname)에서 정상적으로 방화벽이 작동되는지 확인합니다. 


정리

약 40줄의 코드로 방화벽 오픈 여부 확인하는 스크립트를 살펴보았습니다. 지금은 단순히 필요한 서버에 들어가 코드를 실행시키고 터미널에 출력하는 식입니다. 서버에 접속하고, 하나씩 실행시키는 번거로움을 줄이고 자동화를 위해 crontab에 등록하고, 로그 파일에 실행 결과를 적재하는 방법을 사용하면 작업이 훨씬 수월할 것 같습니다. 더 나아가 데이터베이스에 실행결과를 날짜별로 INSERT 하는 방법도 있겠습니다.

 

전체코드

# port_check.py
#!/usr/bin/python3
import socket
import sys
import csv


HOSTNAME = socket.gethostname()

def open_excel_file(excel):
    with open(excel, 'r') as ex:
        next(ex)
        fileReader = csv.reader(ex)
        contents = list(fileReader)
        return contents

def check_socket(hostname: str, ip: str, port: str):
    global HOSTNAME
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    if hostname == HOSTNAME:
        res = sock.connect_ex((ip, int(port)))
        if res == 0:
            print(f"HOST : {ip} - PORT : {port} >>> CONNECTED")
        else:
            print(f"HOST : {ip} - PORT : {port} >>> NOT CONNECTED")
  
      sock.close()
  
def main():
    contents = open_excel_file(sys.argv[1])
    for content in contents:
        check_socket(content[0], content[1], content[2])


if __name__ == '__main__':
    if len(sys.argv) != 2:
        sys.exit("PLEASE INPUT EXCEL FILE")

    main()