capturing TCP packets working

This commit is contained in:
Christian Ulrich 2020-05-21 01:17:37 +02:00
parent 9aa7d66331
commit 7341f61e48
No known key found for this signature in database
GPG Key ID: 8241BE099775A097
4 changed files with 248 additions and 17 deletions

46
network_interface.nim Normal file
View File

@ -0,0 +1,46 @@
from nativesockets import getAddrString
from posix import SockAddr, Sockaddr_in, inet_ntoa, AF_INET
type
NetworkInterface* = object
ipAddress*: string
name*: string
index*: cint
flags*: cuint
NetworkInterfaceError* = object of ValueError
Ifaddrs {.importc: "struct ifaddrs", pure, final,
header: "<ifaddrs.h>".} = object
ifa_next: ptr Ifaddrs # Next item in list
ifa_name: cstring # Name of interface
ifa_flags: cuint # Flags from SIOCGIFFLAGS
ifa_addr: ptr SockAddr # Address of interface
ifa_netmask: ptr SockAddr # Netmask of interface
ifa_ifu: ptr SockAddr # Broadcast / point-to-point address (we don't care about the union)
ifa_data: pointer # Address-specific data
proc getifaddrs(ifap: ptr ptr Ifaddrs): int {.header: "<ifaddrs.h>", importc: "getifaddrs".}
proc freeifaddrs(ifap: ptr Ifaddrs): void {.header: "<ifaddrs.h>", importc: "freeifaddrs".}
proc if_nametoindex(ifname: cstring): cuint {.header: "<net/if.h>", importc: "if_nametoindex".}
proc fromIpAddress*(address: string): NetworkInterface =
var interfaces: ptr Ifaddrs
if getifaddrs(addr interfaces) != 0:
raise newException(NetworkInterfaceError, "getifaddrs failed")
var it = interfaces
while it != nil:
if it.ifa_addr != nil and
it.ifa_addr.sa_family.cint == AF_INET and
it.ifa_addr.getAddrString() == address:
result.ipAddress = address
result.name = $it.ifa_name
result.index = if_nametoindex(result.name).cint
result.flags = it.ifa_flags
break
it = it.ifa_next
freeifaddrs(interfaces)
if result.name == "":
raise newException(NetworkInterfaceError, "interface for given IP address not found")
if result.index <= 0:
raise newException(NetworkInterfaceError, "cannot get interface index")

82
packet_info.nim Normal file
View File

@ -0,0 +1,82 @@
from nativesockets import Port, ntohs, ntohl
from posix import InAddr, inet_ntoa
type
Ether_header {.importc: "struct ether_header", pure, final,
header: "<netinet/if_ether.h>".} = object
ether_dhost: array[6, cuchar]
ether_shost: array[6, cuchar]
ether_type: cushort
Ip {.importc: "struct ip", pure, final,
header: "<netinet/ip.h>".} = object
when cpuEndian == littleEndian:
ip_hl {.bitsize:4.}: cuchar # header length
ip_v {.bitsize:4.}: cuchar # version
else:
ip_v {.bitsize:4.}: cuchar # version
ip_hl {.bitsize:4.}: cuchar # header length
ip_tos: cuchar # type of service
ip_len: cshort # total length
ip_id: cushort # identification
ip_off: cshort # fragment offset field
ip_ttl: cuchar # time to live
ip_p: cuchar # protocol
ip_sum: cushort # checksum
ip_src: InAddr # source address
ip_dst: InAddr # destination address
Tcphdr {.importc: "struct tcphdr", pure, final,
header: "<netinet/tcp.h>".} = object
th_sport: cushort # source port
th_dport: cushort # destination port
th_seq: uint32 # sequence number
th_ack: uint32 # ackkowledgment number
when cpuEndian == littleEndian:
th_x2 {.bitsize:4.}: cuchar # (unused)
th_off {.bitsize:4.}: cuchar # data offset
else:
th_off {.bitsize:4.}: cuchar # (unused)
th_x2 {.bitsize:4.}: cuchar # data offset
th_flags: cuchar # flags
th_win: cushort # window
th_sum: cushort # checksum
th_urp: cushort # urgent pointer
Protocol* = enum
tcp
other
PacketInfo* = object
case protocol*: Protocol
of tcp:
tcpIpSrc*: string
tcpIpDst*: string
tcpPortSrc*: Port
tcpPortDst*: Port
tcpSeqNumber*: uint32
else:
discard
var
ETHERTYPE_IP {.importc: "ETHERTYPE_IP", header: "<netinet/if_ether.h>".}: cushort
IPPROTO_TCP {.importc: "IPPROTO_TCP", header: "<netinet/in.h>".}: cint
proc fromString*(packet: string): PacketInfo =
let etherHeader = cast[ptr Ether_header](packet.cstring)
if ntohs(etherHeader.ether_type) == ETHERTYPE_IP:
let ipHeader = cast[ptr Ip](cast[int](packet.cstring) + sizeof(Ether_header))
let ipSrc = $inet_ntoa(ipHeader.ip_src)
let ipDst = $inet_ntoa(ipHeader.ip_dst)
if ipHeader.ip_p.int == IPPROTO_TCP:
let tcpHeader = cast[ptr Tcphdr](cast[int](ipHeader) + ipHeader.ip_hl.int * 4)
result = PacketInfo(protocol: tcp,
tcpIpSrc: ipSrc,
tcpIpDst: ipDst,
tcpPortSrc: Port(ntohs(tcpHeader.th_sport)),
tcpPortDst: Port(ntohs(tcpHeader.th_dport)),
tcpSeqNumber: ntohl(tcpHeader.th_seq))
else:
result = PacketInfo(protocol: other)
else:
result = PacketInfo(protocol: other)

85
punch.nim Normal file
View File

@ -0,0 +1,85 @@
import
asyncdispatch,
network_interface,
packet_info
from nativesockets import SOCK_RAW, bindAddr, htons
from posix import ioctl, setsockopt, SockAddr, SockLen, SocketHandle
export NetworkInterfaceError
type
Sockaddr_ll {.importc: "struct sockaddr_ll", pure, final,
header: "<linux/if_packet.h>".} = object
sll_family: cushort # Always AF_PACKET
sll_protocol: cushort # Physical-layer protocol
sll_ifindex: cint # Interface number
sll_hatype: cushort # ARP hardware type
sll_pkttype: cuchar # Packet type
sll_halen: cuchar # Length of address
sll_addr: array[8, cuchar] # Physical-layer address
Packet_mreq {.importc: "struct packet_mreq", pure, final,
header: "<linux/if_packet.h>".} = object
mr_ifindex: cint
mr_type: cushort
mr_alen: cushort
mr_address: array[8, cuchar]
PunchHoleError* = object of ValueError
var
AF_PACKET {.importc: "AF_PACKET", header: "<sys/socket.h>".}: cushort
SOL_PACKET {.importc: "SOL_PACKET", header: "<sys/socket.h>".}: cushort
ETH_P_ALL {.importc: "ETH_P_ALL", header: "<linux/if_ether.h>".}: cushort
PACKET_ADD_MEMBERSHIP {.importc: "PACKET_ADD_MEMBERSHIP", header: "<linux/if_packet.h>".}: cushort
PACKET_MR_PROMISC {.importc: "PACKET_MR_PROMISC", header: "<linux/if_packet.h>".}: cushort
proc punchHoleAsClient*(clientAddress: string,
clientPort: Port,
serverAddress: string,
serverPort: Port) {.async.} =
echo "punchHoleAsClient"
# Create a raw packet socket. For accessing outgoing packets we need to use
# ETH_P_ALL which is needed in network byte order, see packet(7) man page.
let rawFd = createAsyncNativeSocket(AF_PACKET.cint,
SOCK_RAW.cint,
htons(ETH_P_ALL).cint)
# Limit capturing of packets to the desired network interface, see
# netdevice(7) man page
let iface = fromIpAddress(clientAddress)
echo "interface: ", iface.name, ", index: ", iface.index
var sa = Sockaddr_ll(sll_family: AF_PACKET,
sll_protocol: htons(ETH_P_ALL),
sll_ifindex: iface.index)
if bindAddr(rawFd.SocketHandle,
cast[ptr SockAddr](addr sa),
sizeof(Sockaddr_ll).SockLen) != 0:
raise newException(PunchHoleError, "cannot bind to interface")
# Enable promiscuous mode, see netdevice(7) man page
var req = Packet_mreq(mr_ifindex: iface.index, mr_type: PACKET_MR_PROMISC)
if setsockopt(rawFd.SocketHandle,
SOL_PACKET.cint,
PACKET_ADD_MEMBERSHIP.cint,
addr req,
sizeof(req).SockLen) != 0:
raise newException(PunchHoleError, "cannot enable promiscuous mode")
while true:
let packet = await rawFd.recv(4000)
if packet == "":
break
echo "packet len: ", packet.len
let packetInfo = fromString(packet)
if packetInfo.protocol == tcp and
packetInfo.tcpIpSrc == clientAddress and
packetInfo.tcpPortSrc.int == clientPort.int and
packetInfo.tcpIpDst == serverAddress and
packetInfo.tcpPortDst.int == serverPort.int:
echo "captured packet, sequence number: ", packetInfo.tcpSeqNumber
break
closeSocket(rawFd)

View File

@ -1,9 +1,11 @@
import import
net,
asyncnet, asyncnet,
asyncdispatch, asyncdispatch,
asyncreadline asyncreadline,
punch
from strutils import format from strutils import format, join
from nativesockets import setSockOptInt from nativesockets import setSockOptInt
from os import paramCount, paramStr from os import paramCount, paramStr
@ -13,21 +15,22 @@ var IP_TTL {.importc: "IP_TTL", header: "<netinet/in.h>".}: cint
proc usage() = proc usage() =
echo("Usage: $# client|server".format(paramStr(0))) echo("Usage: $# client|server".format(paramStr(0)))
#proc punchHoleAsClient(socket: AsyncSocket) {.async.} =
#
proc processInput(client: AsyncSocket) {.async.} = proc processInput(client: AsyncSocket) {.async.} =
while true: while true:
let input = await asyncReadline() let input = await asyncReadline()
await client.send(input & "\c\L") await client.send(input & "\c\L")
proc runClient() {.async.} = proc connectAsClient(clientAddress: string,
clientPort: Port,
serverAddress: string,
serverPort: Port) {.async.} =
var client = newAsyncSocket() var client = newAsyncSocket()
client.setSockOpt(OptReuseAddr, true) client.setSockOpt(OptReuseAddr, true)
client.getFd().setSockOptInt(IPPROTO_IP, IP_TTL, 2) client.getFd().setSockOptInt(IPPROTO_IP, IP_TTL, 2)
client.bindAddr(Port(1234)) client.bindAddr(clientPort, clientAddress)
try: try:
await client.connect("127.0.0.1", Port(4321)) echo "trying to connect"
await client.connect(serverAddress, serverPort)
let (address, port) = client.getPeerAddr() let (address, port) = client.getPeerAddr()
echo("connected to $#:$#!".format(address, port.uint16)) echo("connected to $#:$#!".format(address, port.uint16))
asyncCheck processInput(client) asyncCheck processInput(client)
@ -45,12 +48,15 @@ proc processClient(client: AsyncSocket) {.async.} =
echo("client sent message: $#".format(line)) echo("client sent message: $#".format(line))
await client.send(line & "\c\L") await client.send(line & "\c\L")
proc runServer() {.async.} = proc runServer(serverAddress: string,
serverPort: Port,
clientAddress: string,
clientPort: Port) {.async.} =
var server = newAsyncSocket() var server = newAsyncSocket()
server.setSockOpt(OptReuseAddr, true) server.setSockOpt(OptReuseAddr, true)
server.bindAddr(Port(4321)) server.bindAddr(serverPort)
server.listen() server.listen()
echo("listening on port 4321") echo("listening on port $#".format(serverPort.uint16))
while true: while true:
let client = await server.accept() let client = await server.accept()
let (address, port) = client.getPeerAddr() let (address, port) = client.getPeerAddr()
@ -61,17 +67,29 @@ proc main() =
if paramCount() != 1: if paramCount() != 1:
usage() usage()
return return
let serverAddress = "176.95.209.87"
let serverPort = Port(80)
let clientAddress = $getPrimaryIPAddr(parseIpAddress(serverAddress))
let clientPort = Port(1234)
case paramStr(1) case paramStr(1)
of "client": of "client":
asyncCheck runClient() asyncCheck punchHoleAsClient(clientAddress, clientPort, serverAddress, serverPort)
asyncCheck connectAsClient(clientAddress, clientPort, serverAddress, serverPort)
try:
runForever()
except NetworkInterfaceError as e:
echo(e.msg)
except PunchHoleError as e:
echo(e.msg)
of "server": of "server":
asyncCheck runServer() asyncCheck runServer(serverAddress, serverPort, clientAddress, clientPort)
try:
runForever()
except ValueError:
echo "server got ValueError!"
discard
else: else:
usage() usage()
try:
runForever()
except ValueError:
discard
when isMainModule: when isMainModule:
main() main()