1계층은 물리계층이며 2계층은 데이터 링크층이라고 한다
DataLink층은 상위계층의 패킷을 전달받아 프레임형태로변환하여 물리계층에 전달한다
이 때, 이더넷 헤더에는 상위계층의 프로토콜정보와 src,dst MAC Address정보가 담긴다
헤더
물리계층 |
MAC 프레임 |
|
운영체제에서 볼 수 있는 영역 |
|
Preamble SFD |
DST |
SRC |
TYPE |
DATA |
PADDING |
FCS |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
1>Preamble SFD : 볼 수 없는 영역, 물리(NIC)
2 ~ 7 : 볼 수 있는 영역
2>DST(Destination address) : 프레임을 수신할 MAC주소정보 - 이더넷헤더
3>SRC(Source address) : 프레임을 송신한 호스트의 MAC주소정보 - 이더넷헤더
4>TYPE : 상위 계층 프로토콜 정보를 담고 있다 ( IPv4=0x0800, ARP=0x0806 ) - 이더넷헤더
5>DATA : 상위계층으로부터 전달받은 데이터가 담긴다( 데이터의 최소길이 46byte이어야 한다)
6>PADDING : 데이터가 46byte가 되지 않는다면 채워준다
7>FCS(Frame Check Sequence) : 프레임의 에러 체크를 위한 것 => 장비 단계에서 확인하는 것, 운영체제에서는 볼 수 없다
* 다른 호스트와 통신을 하고자 한다면 그 호스트의 물리주소를 알아야 한다. 그 맥주소로 이더넷 헤더를 완성할 수 있다
이 때, 물리주소를 알아내기 위해 ARP프로토콜을 사용한다
[ ARP ] Address Resolution Protocol : 아이피 주소로 물리 주소를 확인할 수 있는 프로토콜
- IP 주소를 물리적 네트워크 주소( Mac Address 가 포함된 형태 )로 변환하는데 사용되는 프로토콜
- 데이터의 송수신 시 MAC주소를 이용해 통신하는 네트워크계층에서 IP주소로만 프레임을 전달하는 것은 불가능하다
- 따라서 하드웨어의 주소, 즉 MAC주소를 알기 위해 ARP를 사용해 IP와 MAC주소간의 동적매핑을 제공한다
- 처음 IP 주소만 가지고 통신을 시작할 때 해당 IP 주소(targetIP)에서 요청을 처리할 장비가 결정되고 해당 장비의 Mac Address가
요청한 쪽으로 전송이 된다. 이 때부터 실제 통신이 시작되는 것이라 볼 수 있다
- 윈도우: 주기적으로 같은 네트워크에 호스트들의 물리주소를 수집하고 캐시해놓는다
=> arp -a명령어로 확인 가능
- 리눅스: 통신하기 직전에 상대방의 물리 주소를 확인하고 일정시간 동안만 캐시를 유지
=> arp 명령어로 확인가능
# ARP Protocol
같은 네트워크 상에서 호스트들의 물리 주소를 얻어낼 때 사용하는 프로토콜
이전에 만든 sniffer 프로그램에서 packet.eth.type == 0x0806 조건으로 필터링해 얻을 수 있다. ( 아래소스코드있음 )
< ARP 요청 ( Request ) >
data: b'\xff\xff\xff\xff\xff\xff\x1c\xa\x05\x971\xa9\x08\x06 \x00\x01\x08\x00\x06\x04\x00\x01\x1c\xaf\x05\x971\xa9\xc0\xa8\x02I\x00\x00\x00\x00\x00\x00\xc0\xa8\x02\x85 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
[1] 이더넷 헤더 (2계층)
[1] -1 도착지 맥주소
\xff\xff\xff\xff\xff\xff : 맥 어드레스의 브로드캐스팅 주소
( = 같은네트워크상의 모든 장치에게 패킷을 전달한다)
[1] -2 출발지 맥주소
\x1c\xa\x05\x971\xa9 : 송신자의 맥 어드레스
[1] -3 타입
\x08\x06 : 0x0806 = ARP프로토콜
[2] ARP정보
[2] -1 네트워크 타입 (2byte) 이더넷, 토큰링 등등..
\x00\x01 : 이더넷
[2] -2 프로토콜 타입(2byte) IPv4 or IPv6
\x08\x00 : IPv4
[2] -3 하드웨어 길이(1byte) 맥주소의 길이
\x06
[2] -4 프로토콜 길이(1byte) \x04 or \x06
\x04 : IPv4
[2] -5 opcode ( request : \x00\x01 or response : \x00\x02 )
\x00\x01
[2] -6 송신지 MAC Address
\x1c\xaf\x05\x971\xa9
[2] -7 송신지 IP주소
\xc0\xa8\x02I
[2] -8 도착지 MACAddress ( 모르므로 0으로 초기화되어있음 )
\x00\x00\x00\x00\x00\x00 : 도착지의 호스트로부터 해당 정보가 채워져 패킷이 돌아올것이다
=> 맥주소를 알아냈으므로 네트워크 통신이 가능해진다
[2] -9 ( 도착지 IP주소 )
\xc0\xa8\x02\x85
[3] 패딩
해당 데이터의 크기가 46byte보다 작을 때 padding으로 46byte를 만들어준다
< ARP 응답 (Response) >
해당 패킷이 전달되어진 호스트에서 MACAddress정보를 넘겨줄것이다
[ ARP 프로토콜 Class정의 및 헤더를 직접 생성해보자 ]
#vi arp.py [arp정보]
import struct
class Arp:
def __init__(self,raw=None):
self._net_type=raw[:2]
self._proto_type=raw[2:4]
self._mac_len=raw[4:5]
self._proto_len=raw[5:6]
self._opcode=raw[6:8]
self._sender_mac=raw[8:14]
self._sender_ip=raw[14:18]
self._target_mac=raw[18:24]
self._target_ip=raw[24:28]
@property
def header( self ):
return self._hw_type + self._protocol_type + self._hw_size + \
self._protocol_size + self._opcode + self._sender_mac + \
self._sender_ip + self._target_mac + self._target_ip
@property
def net_type(self):
(net_type,) = struct.unpack('!H',self._net_type)
return net_type
@net_type.setter
def net_type(self,type):
type=struct.pack('!H',type)
self._net_type=type
@property
def proto_type(self):
(proto_type,)=struct.unpack('!H',self._proto_type)
@proto_type.setter
def proto_type(self,type):
type=struct.pack('!H',type)
self._proto_type=type
@property
def mac_len(self):
(mac_len,)=struct.unpack('!B',self._mac_len)
return mac_len
@mac_len.setter
def mac_len(self,len):
len=struct.pack('!B',len)
self._mac_len=len
@property
def proto_len(self):
(proto_len,)=struct.unpack('!B',self._proto_len)
return proto_len
@proto_len.setter
def proto_len(self,len):
len=struct.pack('!B',len)
self._proto_len=len
@property
def sender_mac(self):
sender_mac=struct.unpack('!6B',self._sender_mac)
return sender_mac
@sender_mac.setter
def sender_mac(self,mac):
mac=mac.split(':')
for i in range(len(mac)):
mac[i] = int( mac[i] , 16)
self._sender_mac=b''
for i in range(len(mac)):
self._sender_mac += struct.pack('!B',mac[i])
@property
def sender_ip(self):
sender_ip=struct.unpack('!4B',self._sender_ip)
return sender_ip
@sender_ip.setter
def sender_ip(self,ip):
ip=ip.split('.')
for i in range(len(ip)):
ip[i] = int( ip[i] )
self._sender_ip=b''
for i in range(len(ip)):
self._sender_ip += struct.pack('!B',ip[i])
@property
def target_mac(self):
target_mac=struct.unpack('!6B',self._target_mac)
target_mac='%02x:%02x:%02x:%02x:%02x:%02x' %target_mac
return target_mac
@target_mac.setter
def target_mac(self,mac):
mac = mac.split(':')
for i in range(len(mac)):
mac[i] = int( mac[i] , 16 )
self._target_mac = b''
for i in range(len(mac)):
self._target_mac += struct.pack('!B',mac[i])
@property
def target_ip(self):
target_ip=struct.unpack('!4B',self._target_ip)
return target_ip
@target_ip.setter
def target_ip(self,ip):
ip = ip.split('.')
for i in range(len(ip)):
ip[i] = int( ip[i] )
self._target_ip = b''
for i in ragne(len(ip)):
self._target_ip += struct.pack('!B',ip[i])
#vi eth.py [이더넷헤더] - 전송관련 담당
import struct
class Eth:
def __init__(self,raw=None):
self._dst = raw[:6]
self._src = raw[6:12]
self._type = raw[12:14]
@property
def header( self ):
return self._dst + self._src + self._type
@property
def dst(self):
dst = struct.unpack('!6B',self._dst)
dst = '%02x:%02x:%02x:%02x:%02x:%02x' %dst
return dst
@dst.setter
def dst(self,dst):
dst = dst.split(':')
for i in range(len(dst)):
dst[i] = int( dst[i] , 16 )
self._dst = b''
for i in range(len(dst)):
self._dst += struct.pack('!B', dst[i])
@property
def src(self):
src = struct.unpack('!6B',self._src)
src = '%02x:%02x:%02x:%02x:%02x:%02x' %src
return src
@src.setter
def src(self,src):
src = src.split(':')
for i in range(len(src)):
src = int ( src[i] , 16 )
self._src = b''
for i in range(len(src)):
self._src += struct.pack('!B', src[i] )
@property
def type(self):
(type,) = struct.unpack('!H', self._type)
return type
@type.setter(self,type):
type = struct.pack('!H', type)
self._type = type
#vi arping.py - setter를 통해 헤더에 정보를 추가하고 패킷전송
import socket
from header.eth import *
from header.arp import *
eth = Eth()
arp = Arp()
eth.dst = 'FF:FF:FF:FF:FF:FF'
eth.src = '00:0c:29:f0:62:73'
eth.tpye = 0x0806
arp.net_type = 0x0001
arp.proto_type = 0x0800
arp.mac_len = 6
arp.proto_len = 4
arp.opcode = 1
arp.sender_mac ='00:0c:29:f0:62:73'
arp.sender_ip = '192.168.2.233'
arp.target_mac = '00:00:00:00:00:00'
arp.target_ip = '192.168.1.110'
#vi sniffer.py - eth0장치로 들어오고 나가는 정보를 출력
import socket
import struct
from packet import *
raw = socket.socket(socket.PF_PACKET,socket.SOCK_RAW)
raw.bind(('eth0',socket.SOCK_RAW))
while True:
data,addr=raw.recvfrom(65535)
packet = Packet(data)
if packet.eth.tpye == 0x0806 and ( packet.arp.sender_ip == '192.168.2.233' or packet.arp.target_ip == '192.168.2.233' ):
print( "data : " , data )
print( packet.eth.src + ' -> ' + packet.eth.dst + ' type: ' + hex(packet.eth.type) )
print( 'opcode : ' + str( packet.arp.opcode ) )
print( 'sender ip : ' + packet.arp.sender_ip + 'target ip : ' + packet.arp.target_ip )
print( 'sender mac : ' + packet.arp.sender_mac + 'target mac : ' + packet.arp.target_mac )
print()
[ 실행결과 ]
[ 그림1 ]
opcode : 1 : request
같은 네트워크 상에 있는 192.168.1.110 호스트에게 arp패킷을 전송했다
opcode : 2 : response
192.168.1.110 호스트의 MAC Address를 arp패킷의 정보에 추가해서 나에게 응답해준다
arp패킷을 통해 상대의 맥 주소를 알아내어 이제는 네트워크 통신이 가능해진다
* 참고
아래 그림2와 그림3은 Arp와 Eth클래스의 setter를 통해서 각 멤버에게 데이터를 할당하는 모습입니다
아래 처럼 직접 입력하지 않고 arping.py 코드를 짜서 쉽게 멤버변수들을 초기화 시킬 수 있다
[ 그림2 ]
[ 그림3 ]