반응형

필자는 pcap 라이브러리를 사용하며 다양한 프로그램을 구현해본 경험이 있다.

최근 python으로도 pcap 라이브러리가 존재한다는 것을 알게되었다.

바로 PyPCAP인데, 자료가 많지 않더라. 공부해보자.


1. PyPCAP?

pypcap을 이야기하기 전에 pcap에 대해 먼저 알아야한다.

 

pcap이란 Packet Capture의 약자인데, 네트워크상의 패킷을 캡쳐하기 위한 API이다.

윈도우는 Winpcap, 리눅스에선 libpcap을 사용하여 이용 가능하다.

 

pypcap은 말그대로 python 버전의 pcap 라이브러리이다.

이를 이용하여 python 환경에서 네트워크 트래픽을 손쉽게 캡쳐하고, 분석 가능할 수 있다.

 

*공식 문서와 설명은 다음 링크에 있다.




2. 사용법

pypcap을 사용하기 위해선 pypcap을 먼저 설치해야한다.

이후 pcap을 import하여 사용하면 된다.

 

1. 설치

pip intall pypcap

  • python3의 경우 pip3 install pypcap
  • libpcap-dev, python-dev 패키지 의존

 

2. 사용법

import pcap




3. 실습/예제

이제 pypcap을 이용하여 간단한 코딩을 진행하자.

 

네트워크 패킷을 캡쳐하게 되면 호스트에선 Ethernet Header의 MAC Frame 부분부터 잡기 시작한다.

아래와 같이 첫 6바이트는 Destination의 MAC 주소를 가리키고, 다음 6바이트는 Source의 MAC 주소를 가리킨다.

 

다음은 Destination MAC주소와 Source MAC 주소를 출력하는 코드의 예문이다.

 

import pcap #pypcap 라이브러리

sniffer = pcap.pcap(name=None, promisc=True, immediate=True, timeout_ms=50)

for ts, pkt in sniffer:
    print('Dst MAC - ', end='', flush=True)
    print(':'.join('%02X' % i for i in pkt[0:6])) #패킷의 0번부터 5번 바이트까지 출력
    print('Src MAC - ', end='', flush=True)
    print(':'.join('%02X' % i for i in pkt[6:12])) #패킷의 6번부터 11번 바이트까지 출력
    print()

 

위와 같이 매우 짧은 코드로 잡힌 패킷의

출발지 맥주소와 도착지 맥주소를 출력할 수 있다.

 

코드를 잠시 살펴보자.

 

  • sniffer = pcap.pcap(name = None, promisc = True, immediate = True, timeout_ms = 50)
    • name = None > 디바이스 이름이다. None을 두면 디폴트값이 들어가게 되고, 작은따옴표 안에 직접 디바이스 명을 넣어줄 수 있다.
    • promisc = True > 프리큐어스(무차별) 모드이다. 해당 옵션을 True, 곧 사용하겠다는 뜻이다. 해당 옵션으로 모든 패킷을 캡쳐 가능하다.
    • immediate = True > immediate 옵션이다. 해당 옵션을 True로 설정하여 지연문제를 없앨 수 있다.
    • timeout_ms = 50 > 말그대로 타임아웃 시간이다. ms단위로 50이므로 0.05초로 설정하였다.
    • 해당 return 값을 sniffer에 반환한다.

print(sniffer)

  • for ts, pkt in sniffer
    • sniffer 객체의 첫번째 값은 timestamp, 두번째 값은 packet으로 반복문을 돌겠다는 의미이다.
    • 이후 pkt[idx] 형식으로 네트워크 패킷의 특정 위치 인덱스 값을 가져올 수 있다.



 

4. 응용

pypcap은 dpkt나 socket 모듈 등과 함께 편리하게 사용 가능하다.

다음은 dpkt 모듈을 이용하여 Ethernet의 정보를 파싱하여 출력하는 예문이다.

 

import pcap
import dpkt

sniffer = pcap.pcap(name='eth0', promisc=True, immediate=True, timeout_ms=50)
sniffer.setfilter('tcp and port 443')

def mac_addr(address):
    return ':'.join('%02X' % dpkt.compat.compat_ord(b) for b in address)

for ts, pkt in sniffer:
    eth = dpkt.ethernet.Ethernet(pkt)
    #ip = eth.data
    #tcp = ip.data
    print('Ethernet INFO - ', mac_addr(eth.dst), mac_addr(eth.src), eth.type)
  • eth0 디바이스를 이용하여 캡쳐를 진행하고,
  • setfilter() 함수를 통해 tcp 패킷과 443번 포트를 필터링
  • 이후 dpkt의 Ethernet 클래스로 ethernet 부분을 파싱
  • 해당 함수의 변수를 사용해 원하는 값을 출력

 

 

*eth.data, ip.data 등을 이용해 IP와 TCP Header에 대한 파싱도 손쉽게 가능하다.



4. 마무리

위와 같이 해당 모듈을 이용해 자신이 원하는 대로 네트워크 패킷을 캡쳐, 분석 가능하다.

 

  • 느낀점
    • C++보다 훨씬 짧은 코드로 원하는 결과를 표현 가능
    • python의 장점을 잘 살린 pcap 모듈
    • 하지만 관련 자료가 많지 않아 해당 라이브러리의 공식 문서를 찾는 수고가 필요

 

계속해서 공부하다보면 네트워크를 이용하는 프로그램을 손쉽게 만들 수 있게 될 것이다.


(이렇게 한글로 된 pypcap 자료 갯수에 이바지하였다.)

반응형

'Programming > Network' 카테고리의 다른 글

[소켓 프로그래밍] TCP Echo-Client, Server 구현  (0) 2020.06.09
반응형

C++을 이용해 소켓프로그래밍을 해보려한다.

소켓에 대한 개념은 너무 방대하여 간단하게 다루고

프로그램을 짜는데 중점을 두고 포스팅하려한다.

함께, 에코 서버와 에코 클라이언트를 프로그래밍해보자.


1. 소켓(Socket) 프로그래밍

소켓 : 네트워크에서 서버와 클라이언트가 서로 특정 포트를 이용하여 양방향 통신하도록 만들어주는 소프트웨어 장치

소켓 프로그래밍 : 소켓을 사용하여 네트워크를 구성해나가는 과정

 

2. 소켓 API 흐름

서버소켓과 클라이언트 소켓의 흐름은 다음과 같다.

 

서버 소켓 >

  1. 소켓 생성 : Create
  2. 서버가 사용할 주소 지정하여 결합(IP, Port) : Bind
  3. 연결 요청 대기 : Listen
  4. 요청 수신시 받기 : Accept
  5. 연결 수립시(Established) 데이터 송수신 : send/recv
  6. 송수신 완료, 소켓 닫기 : Close

클라이언트 소켓 >

  1. 소켓 생성 : Create
  2. 서버 연결요청 : Connect
  3. 연결 요청 받으면 데이터 송수신 : send/recv
  4. 송수신 완료, 소켓 닫기 : Close

그림으로 정리하면 다음과 같다.

3. Echo-Client

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;

void usage(char *argv){
    cout << "Usage : " << argv << " [ip] [port]" << endl;
    cout << "Example) " << argv << " 192.168.0.12 1234" << endl;
}

int main(int argc, char *argv[]){
    if(argc != 3){ //인자가 3개가 아니면 usage를 출력
        usage(argv[0]);
        return -1;
    }

    struct sockaddr_in addr_server = {}; // 주소체계 구조체 선언

    memset(&addr_server, 0, sizeof(addr_server));
    addr_server.sin_family = AF_INET; // IPv4 통신, 기본 인터넷 프로토콜 지정
    addr_server.sin_addr.s_addr = inet_addr(argv[1]); // 첫번째 인자 IP 대입
    addr_server.sin_port = htons(atoi(argv[2])); // 두번째 인자 PORT 대입

    char w_buff[256]; // 쓰기용 버퍼 선언
    char r_buff[256]; // 읽기용 버퍼 선언

    int sock_client = socket(AF_INET, SOCK_STREAM, 0); // 소켓 생성(IPv4, TCP, 기본프로토콜)
    if(sock_client == -1){
        cout << "socket error" << endl;
        close(sock_client);
        exit(1);
    }

    if(connect(sock_client, (struct sockaddr*) &addr_server, sizeof(addr_server)) == -1){ // 연결 요청
        cout << "connect error" << endl;
        close(sock_client);
        exit(1);
    }

    while(1){ // 연결 수락시 반복문
        memset(r_buff, 0, 256); // 읽기 버퍼 초기화
        cin >> w_buff; // 쓰기 버퍼에 문자열 입력
        if(strlen(w_buff)>255) break; // 버퍼 오버플로그 방지
        int write_chk = write(sock_client, w_buff, strlen(w_buff)); // 작성 길이만큼 write(전송)
        if(write_chk == -1){
            cout << "write error" << endl;
            break;
        }
        int read_chk = read(sock_client, r_buff, sizeof(r_buff)-1); // 읽기 버퍼사이즈-1 만큼 read(읽기)
        if(read_chk == -1){
            cout << "read error" << endl;
            break;
        }else{
            r_buff[strlen(r_buff)] = '\n';
            cout << r_buff; // 버퍼 출력
        }
    }
    close(sock_client); // 연결 종료
    return 0;
}

 

 

4. Echo-Server

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;

void usage(char *argv){
    cout << "Usage : " << argv << " [port]" << endl;
    cout << "Example) " << argv << " 1234" << endl;
}

int main(int argc, char *argv[]){
    if(argc != 2){ // 인자가 2개가 아니면 usage 출력
        usage(argv[0]);
        return -1;
    }

    char buff[256]; // 읽기, 쓰기용 버퍼 선언

    struct sockaddr_in addr_server = {}; // 주소체계 구조체 선언
    struct sockaddr_in addr_client = {};
    socklen_t addr_client_len = sizeof(addr_client_len); // 길이 계산

    memset(&addr_server, 0, sizeof(addr_server)); // 초기화
    addr_server.sin_family = AF_INET; // IPv4 인터넷 프로토콜
    addr_server.sin_port = htons(atoi(argv[1])); // 첫번째 인자 PORT 지정
    addr_server.sin_addr.s_addr = htonl(INADDR_ANY); // Anybody
 

    int sock_server = socket(AF_INET, SOCK_STREAM, 0); // 소켓 생성
    if(sock_server == -1){
        cout << "socket error" << endl;
        close(sock_server);
        exit(1);
    }

    if(bind(sock_server, (sockaddr*) &addr_server, sizeof(addr_server)) == -1){ // 주소 지정
        cout << "bind error" << endl;
        close(sock_server);
        exit(1);
    }

    if(listen(sock_server, 3) == -1){ // 연결 대기
        cout << "listen error" << endl;
        close(sock_server);
        exit(1);
    }

    int sock_client = accept(sock_server, (sockaddr*) &addr_client, &addr_client_len); // 연결 수락
    if(sock_client == -1){
        cout << "accept error" << endl;
        close(sock_server);
        exit(1);
    }

    while(1){
        memset(buff, 0, 256); // 버퍼 초기화
        int read_chk = read(sock_client, buff, sizeof(buff)-1); // 버퍼크기-1만큼 read(읽기)
        if(read_chk == -1){
            cout << "read error" << endl;
            break;
        }
        buff[strlen(buff)] = '\n';
        cout << buff; // 버퍼 출력
        int write_chk = write(sock_client, buff, strlen(buff)); // 버퍼 사이즈만큼 write(전송)
        if(write_chk == -1){
            cout << "write error" << endl;
            break;
        }
    }
    close(sock_server); // 연결 종료
    return 0;
}

 

 

5. 실행결과

1234번 포트로 server를 실행시키고 client에서 localhost와 1234번 포트로 접속을 시도했다.

client에서 "echotest"와 "hellworld" 문자열을 입력해보았고 실행결과는 다음과 같다.

 

echo-server-main.cpp

 

echo-client-main.cpp


잘못된 정보나 코드 제보 받으며, 여기까지 Echo-Client, Echo-Server 프로그램 개발 포스팅을 마친다.

 

 

반응형

'Programming > Network' 카테고리의 다른 글

[Network] pypcap 공부, 실습  (0) 2020.08.04

+ Recent posts