0

With PyQt5, I have successfully set up a QWebEngineUrlRequestInterceptor subclass, and I can modify the headers and data of the web request. I am building this for a VPN-like app, where the request gets converted into a packet (sent with scapy's IP() object or a similar module), encrypted and sent to another address, who will decrypt and convert the packet data back into a QWebEngine request. My question is how can one convert a web request intercepted with PyQt5 into an IP packet format, and vice-versa?

Here is the interceptor code:

#Custom url interceptor for modifying headers
#and the url before sending it on
class UrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
  def __init__(self,parent=None):
    super(QWebEngineUrlRequestInterceptor,self).__init__(parent)

  def interceptRequest(self,info):
    #Modify the actual request here - todo: block the
    #request here, packet simulate etc...
    info.setHttpHeader(b"Accept-Language",InterClassVariables.languageCode.encode())
SamG101
  • 488
  • 1
  • 7
  • 18
  • What is the object type of this "IP packet" format? Is it raw bytes like a packet capture? Is it a packet built with Scapy's `IP()`? Can you post EXACTLY what the end result should look like? – Ross Jacobs Dec 12 '19 at 20:15
  • It's a packet built with Scapy's `IP()` object – SamG101 Dec 13 '19 at 16:11

1 Answers1

1

The problem is that QWebEngineUrlRequestInterceptor interacts with HTTP (layer 7) when you want IP info (layer 3). QWebEngineUrlRequestInfo does not have any IP properties (again it's at layer 7).

While it's possible to use Qt's QHostInfo, it's simpler to use vanilla Python. Here, we are using netifaces. While it's not in the standard library, alternatives are more complex, platform-dependent, or require an internet connection.

import socket

from PyQt5.Qt import QUrl
from PyQt5.Qt import QWebEngineUrlRequestInterceptor
from PyQt5.Qt import QWebEngineUrlRequestInfo
import netifaces
from scapy.all import IP

#Custom url interceptor for modifying headers
#and the url before sending it on
class UrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
  def __init__(self,parent=None):
    super(QWebEngineUrlRequestInterceptor,self).__init__(parent)
    self.info = None

  def interceptRequest(self, info: QWebEngineUrlRequestInfo):
    #Modify the actual request here - todo: block the
    #request here, packet simulate etc...
    self.info = info
    self.info.setHttpHeader(b"Accept-Language",
      InterClassVariables.languageCode.encode())
    self.sendPacket()

  def getDestIp(self):
    qUrl = self.info.requestURL()
    url = qUrl.toString()
    dest_ip = socket.gethostbyname(url)
    return dest_ip

  def sendPacket()
    src_ip = netifaces.gateways()['default'][netifaces.AF_INET][0]
    dest_ip = get_dest_ip(self.info)
    ip_layer = IP(src=src_ip, dst=dest_ip)
    other_layers = OTHER_PROTOCOLS() # FIXME
    pkt = ip_layer/other_layers
    send(pkt) # Choose whichever send function is most appropriate here

If we print the packet (without other layers), we should get something similar to this:

$ python project.py -show-packet # Pseudo cli
###[ IP ]###
  version   = 4
  ihl       = None
  tos       = 0x0
  len       = None
  id        = 1
  flags     =
  frag      = 0
  ttl       = 64
  proto     = ip
  chksum    = None
  src       = 172.31.98.1
  dst       = 151.101.65.69
  \options   \

Ross Jacobs
  • 2,962
  • 1
  • 17
  • 27
  • Thanks very much, so far working. One thing - how would I convert the received packet data back into a `QWebEngineUrlRequestInfo` object? – SamG101 Dec 17 '19 at 17:22
  • You would need to implement a receive_packet function and a function to construct a requestInfo object. You may need to use the machinery of the Interceptor superclass to do the latter. – Ross Jacobs Dec 17 '19 at 17:56