Developing Hello World Plugin
In this guide you will learn how to develop a simple plugin that logs "Hello World" on bootstrap, and blocks requests to specific websites.
The resulting project can be found here.
It assumes you are already familiar with Java.
Preparing Main Class
First, create an arbitrary package and a class in it, for example, com.example.plugin.MyPlugin
.
Every plugin should extend PowerTunnelPlugin
class to get access to plugin lifecycle:
package com.example.plugin;
import io.github.krlvm.powertunnel.sdk.plugin.PowerTunnelPlugin;
public class MyPlugin extends PowerTunnelPlugin {
...
}
Listening to server lifecycle
The key plugin method is onProxyInitialization
- it is being called after injecting the last plugin and before starting the proxy server. During the initialization, you can modify proxy server configuration and bind your server lifecycle listeners - you can listen to server status change (starting, running, stopping, not running) and proxy server lifecycle events - control the traffic when it's going from client to proxy, from proxy to server, from server to proxy, and from proxy to client, and control DNS requests.
To bind a server lifecycle listener, use method registerServerListener
, which accepts ServerListener
instance as argument.
Tip
It's more convenient to extend ServerAdapter
than ServerListener
@Override
public void onProxyInitialization(ProxyServer proxy) {
System.out.println("Hello World!");
registerServerListener(new ServerAdapter() {
@Override
public void onProxyStatusChanged(ProxyStatus status) {
System.out.println("status = " + status.name());
}
});
}
Listening to proxy lifecycle
Blocking requests
To listen to proxy lifecycle events, you need to register your listener in onProxyInitialization
using method registerProxyListener
, which accepts ProxyListener
instance and (optionally) priority for the given listener instance.
Tip
It's more convenient to extend ProxyAdapter
than ProxyListener
In this article, we are developing a plugin that restricts access to some websites, the best time for filtering is the transfer of a packet from the client to the proxy - corresponding ProxyListener lifecycle callback is onClientToProxyRequest
.
For example, we can restrict access to google.com
using following code:
@Override
public void onProxyInitialization(ProxyServer proxy) {
registerProxyListener(new ProxyAdapter() {
@Override
public void onClientToProxyRequest(ProxyRequest request) {
if(request.getHost().endsWith("github.com")) {
request.setBlocked(true);
}
}
});
}
setBlocked
is equivalent to calling method setResponse
with body text "Access denied by proxy server" and status code 403. Request is considered blocked if response is not null, if response is null, the request goes further along the chain. You can check if a request is blocked by calling the isBlocked
method.
Important
It's highly recommended to check if the request is blocked before inspecting it, because it might be blocked by another plugin and there's no need to modify it as it will not reach its destination.
If you want to set custom response, use setResponse
method:
@Override
public void onProxyInitialization(ProxyServer proxy) {
registerProxyListener(new ProxyAdapter() {
@Override
public void onClientToProxyRequest(@NotNull ProxyRequest request) {
if(request.isBlocked()) {
// Request is already blocked by another plugin
return;
}
if(request.getHost().endsWith("github.com")) {
request.setResponse(proxy.getResponseBuilder("This website is blocked", 403)
.contentType("text/plain")
.build()
);
}
}
});
}
Faq
Why can't I see the blocking message?
You may notice that website you blocked just doesn't load without showing any message.
This is because most likely (in 99% of cases) you are loading the site over HTTPS, where S stands for Secure: you cannot just interfere with HTTPS traffic without a MITM attack. Information on using MITM is found below.
If you're looking for plain HTTP website for testing, check neverssl.com
Modifying responses
To modify the response, it's recommended to use onProxyToClientResponse
lifecycle method.
Since most websites use HTTPS, you need to use a MITM attack to decrypt packets.
You should also make sure that you're working with the full packet and not with a chunk.
PowerTunnel provides a very simple way to do this.
Caution
You will need to install a Root CA on every machine, the traffic that you are MITMing.
Some webservers validates the certificate, so they could break in unexpected ways.
@Override
public void onProxyInitialization(ProxyServer proxy) {
proxy.setMITMEnabled(true);
proxy.setFullResponse(true);
}
For example, you want to add "Hello World" to the beginning of the web page.
You will need to check that you aren't working with a chunk.
Warning
Make sure that MITM is enabling before modifying request to make sure you will not break encrypted HTTPS packet
@Override
public void onProxyToClientResponse(@NotNull ProxyResponse response) {
if(!proxy.isMITMEnabled()) return;
if(!response.isDataPacket()) return;
response.setRaw("<b>Hello World!</b>" + response.raw());
}
Creating manifest
To make your plugin work, you need to provide a manifest, as described in previous article.
Assuming our main class is named MyPlugin
and it's located in package com.example.plugin
, we should create a plugin.ini
in project resources with the following content:
id: my-plugin
version: 1.0
versionCode: 1
name: My Plugin
description: Hello World Plugin
author: Unknown Author
homepage: https://github.com/krlvm/PowerTunnel-Plugin-Template
mainClass: com.example.plugin.MyPlugin
targetSdkVersion: 99
Testing plugin
Since Desktop and Android editions are sharing the same codebase, it's easier to test the plugin on Desktop version, while it is guaranteed that it will work in the same way on Android.
Note
Android uses its own Java Virtual Machine — Android Runtime (ART), previously known as Dalvik.
To produce Android-compatible jarfile, the plugin code should be compiled to .dex
file.
PowerTunnel uses D8 tool from Android SDK to produce Android-compatible jars, it will be available later.
To test plugin, you need to produce a .jar
artifact and put the jarfile into plugins
directory of PowerTunnel distribution directory. If the proxy server was running at the moment you pasted the file into plugins directory, you need to completely restart PowerTunnel.
To be continued
The documentation is not yet complete, if you have any suggestions or questions, please open a new issue on GitHub.