Server 용 Swift 로 Packet Capture Program 작성하기

현재 스위프트 지원 서버는 우분투만이 가능하다.
센토스도 설정을 맞추면 가능하긴 한데 정신건강을 위해 우분투를 사용하자.

Ubuntu 16.04 에서 Swift 를 설치하자

$ sudo apt-get install clang libicu-dev

이제 swift.org 에서 스위프트 압축된 파일을 다운로드 받는다.

$ wget -q -0 -https://swift.org/keys/all-keys.asc

$ tar xzf swift-<VERSION>-<PLATFORM>.tar.gz
이 명령은 /Home/user 밑에서 실행하여 하위로 푼다.

$ export PATH=/Home/user/usr/swift-4.0.3-RELEASE-ubuntu16.04/usr/bin:"${PATH}"
패스를 추가했다.

$ swift
Welcome to Swift version 4.0.3 어쩌고 나오면 성공한 것이다.

이제 libpcap Library 를 설치해보자.
$ sudo apt-get install libpcap-dev

이제 Swift 로 Packet Capture 할 준비가 되었다.
Cpcap 이란 폴더를 생성하자.
$ mkdir Cpcap
$ cd Cpcap

여기서 파일을 2개를 만들것이다.
$ touch Package.swift
$ touch module.modulemap

Package.swift 내용을 이렇다.
import PackageDescription
let package = Package(name: "Cpcap")

module.modulemap 내용을 이렇게 작성한다.
module Cpcap [system] {
  header "/usr/include/pcap.h"
  link "pcap"
  export *
}

이제 git 을 생성해야 한다.
/Pcap 폴더에 위치한 지 확인 후 생성한다.
$ git init
$ git add Package.swift module.modulemap
$ git commit -m "Initial commit"
$ git tag 0.0.1

이제 Swift 에서 Pcap 을 패키지로 불러 올 수 있게 되었다.
일단 폴더를 Pcap 상위 폴더로 간다.
$ cd ..
$ ls
결과가 Pcap 폴더가 보여야 한다.
여기서 이제 서버용 패킷 캡쳐 프로그램일 ServerSideSwiftTap 를 생성한다.
$ mkdir ServerSideSwiftTap
$ cd ServerSideSwiftTap

이제 파일 2개를 만들 것이다.
$ touch Package.swift
$ touch main.swift

Package.swift 내용은 이렇다
import PackageDescription
let package = Package(name: "ServerSideSwiftTap", dependencies: [.Package(url: "../Cpcap", version: Version(0.0.1)..<Version(1.0.0))])

main.swift 내용을 이렇다.

import Foundation
import MongoKitten
import Cpcap
import Glibc

let server = try Server("mongodb://localhost:27017")
let database = server["packet_db"]

if server.isConnected {
    print("Connected successfully to server")
}

let MAX_RECV_SIZE:Int32 = 65535
let PROMISC:Int32 = 1
let TIMEOUT:Int32 = 100

var frameNo = 0

let ver = UnsafePointer<Int8>(pcap_lib_version())!
print("pcap version -> \(String(cString: ver))")

var errbuf = UnsafeMutablePointer<Int8>.allocate(capacity: Int(PCAP_ERRBUF_SIZE))
let dev = pcap_lookupdev(errbuf)
var devName = "ens2"
if let dev = dev {
    devName = String(cString: dev)
    print("found \(devName)")
} else {
    print("Could not get dev")
}

let pcapSession = pcap_open_live(devName, MAX_RECV_SIZE, PROMISC, TIMEOUT, errbuf)

let status = pcap_loop(pcapSession, -1, { (_, packHeader, packetData) in
   
    // 헤더 받은 순서 및 시간 및 크기 표시
    var tv_sec = Double(packHeader!.pointee.ts.tv_sec) + 60*60*9 // +0900
    let sec = Int(tv_sec.truncatingRemainder(dividingBy: 60))
    tv_sec = tv_sec / 60
    let min = Int(tv_sec.truncatingRemainder(dividingBy: 60))
    tv_sec = tv_sec / 60
    let hour = Int(tv_sec.truncatingRemainder(dividingBy: 24))
    frameNo = frameNo + 1
    let str_Frame = String(format: "Frame %d: %d byte / %d byte %02d:%02d:%02d.%06ld", frameNo, packHeader!.pointee.caplen, packHeader!.pointee.len, hour, min, sec, packHeader!.pointee.ts.tv_usec)
    //print(str_Frame)
   
    // 패킷분석
    let data = Data(bytes: packetData!, count: Int(packHeader!.pointee.caplen))
    let arrayPacketData = Array(data)
    let pa = PacketAnalyser.sharedInstance
    //pa.process(packetDataArray: arrayPacketData)
    let packetData = pa.processPacket(packetDataArray: arrayPacketData)
    if packetData.src_ip != "" {
        let userDocument: Document = [
   "hour": String(format: "%02d", hour),
   "minute": String(format: "%02d", min),
            "second": String(format: "%02d", sec),
            "mili_second": String(format: "%06d", packHeader!.pointee.ts.tv_usec),
            "src_ip": packetData.src_ip,
            "src_port": packetData.src_port,
            "dst_ip": packetData.dst_ip,
            "dst_port": packetData.dst_port,
            "data": packetData.data
        ]
        do {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyyMMdd"
            let str_date = dateFormatter.string(from: Date())
            let collection_name = "tcp_collection_\(str_date)"
            let collection = database[collection_name]
            try collection.insert(userDocument)
            print("success insert!!")
        } catch {
            print("tcp_collection insert error -> \(error.localizedDescription)")
        }
    }
   
}, nil)

pcap_close(pcapSession)

패킷분석을 위한 packetAnalyser.swift 를 작성한다.

import Foundation

class PacketAnalyser {
    static let sharedInstance = PacketAnalyser()
   
    func process(packetDataArray: Array<UInt8>)  {
        // Ethernet Ether_Type = 08 00 이면 IP
        let dst_str = packetDataArray[0...5].map { String(format: "%02X", $0) }.joined(separator: ":")
        let src_str = packetDataArray[6...11].map { String(format: "%02X", $0) }.joined(separator: ":")
        let ether_type = packetDataArray[12...13].map { String(format: "%02X", $0) }.joined(separator: " ")
        print("Ethernet: \(src_str) -> \(dst_str), \(ether_type) ")
        if ether_type == "08 00"
        {
            // IP
            let total_packet_size = UInt16(bigEndian: Data(bytes: packetDataArray[16...17]).withUnsafeBytes {$0.pointee})
            let protocol_id = UInt8(bigEndian: packetDataArray[23]) // 1 <- ICMP, 2 <- IGMP, 6 <- TCP, 17 <- UDP
            let src_ip = packetDataArray[26...29].map { String(format: "%d", $0) }.joined(separator: ".")
            let dst_ip = packetDataArray[30...33].map { String(format: "%d", $0) }.joined(separator: ".")
            print("IP: \(src_ip) -> \(dst_ip), \(protocol_id), \(total_packet_size) bytes")
            // TCP
            if protocol_id == 6
            {
                let src_port = UInt16(bigEndian: Data(bytes: packetDataArray[34...35]).withUnsafeBytes {$0.pointee})
                let dst_port = UInt16(bigEndian: Data(bytes: packetDataArray[36...37]).withUnsafeBytes {$0.pointee})
                let seq_no = UInt32(bigEndian: Data(bytes: packetDataArray[38...41]).withUnsafeBytes {$0.pointee})
                let ack_no = UInt32(bigEndian: Data(bytes: packetDataArray[42...45]).withUnsafeBytes {$0.pointee})
                let windows_size = UInt16(bigEndian: Data(bytes: packetDataArray[48...49]).withUnsafeBytes {$0.pointee})
                let check_sum = UInt16(bigEndian: Data(bytes: packetDataArray[50...51]).withUnsafeBytes {$0.pointee})
                print("TCP -> PORT: \(src_port) -> \(dst_port), seq: \(seq_no), ack: \(ack_no), window_size: \(windows_size), checksum: \(check_sum)")
                let real_data = packetDataArray[54...].map { String(format: "%C", $0) }.joined()
                print("Real Data -> \(real_data) \n")
            }
        }
    }

    func processPacket(packetDataArray: Array<UInt8>) -> PacketData  {
        var packetData = PacketData()
        // Ethernet Ether_Type = 08 00 이면 IP
        let ether_type = packetDataArray[12...13].map { String(format: "%02X", $0) }.joined(separator: " ")
        if ether_type == "08 00"
        {
            // IP
            let protocol_id = UInt8(bigEndian: packetDataArray[23]) // 1 <- ICMP, 2 <- IGMP, 6 <- TCP, 17 <- UDP
            let src_ip = packetDataArray[26...29].map { String(format: "%d", $0) }.joined(separator: ".")
            let dst_ip = packetDataArray[30...33].map { String(format: "%d", $0) }.joined(separator: ".")          
            // TCP
            if protocol_id == 6
            {
                let src_port = UInt16(bigEndian: Data(bytes: packetDataArray[34...35]).withUnsafeBytes {$0.pointee})
                let dst_port = UInt16(bigEndian: Data(bytes: packetDataArray[36...37]).withUnsafeBytes {$0.pointee})
                //let real_data = packetDataArray[54...].map { String(format: "%C", $0) }.joined()              
let real_data = packetDataArray[54...].map { String(format: "%02X", $0) }.joined()
                packetData.src_ip = src_ip
                packetData.src_port = "\(src_port)"
                packetData.dst_ip = dst_ip
                packetData.dst_port = "\(dst_port)"
                packetData.data = real_data
            }
        }
        return packetData
    }

}

struct PacketData: Codable {
    var src_ip: String = ""
    var src_port: String = ""
    var dst_ip: String = ""
    var dst_port: String = ""
    var data: String = ""
}

이제 컴파일을 해본다
해당 폴더(ServerSideSwiftTap)에서 다음 명령을 수행한다.
$ swift build

이제 .build 폴더가 생성되었을 것이다.
$ sudo ./.build/debug/ServerSideSwiftTap

몽고디비에 자료가 저장이 되면 성공한 것이다.

댓글

이 블로그의 인기 게시물

한글 2010 에서 Ctrl + F10 누르면 특수문자 안뜰 때

아이폰에서 RFID 사용하는 방법

VCC 와 GND 는 무엇일까?