-1

How to Limit the Internet Speed of a VPN Connection on Android programmatically.

I have managed to control the upload speed using the OpenVPN "shaper" option, but I want to control both upload and download speeds.

Thank you.

5
  • Hi, Sometimes I also require to do similar kind of things for my android environment. I normally use Charles Proxy for this. You can check this out from here : charlesproxy.com Commented Jun 6 at 6:53
  • @ASMSayem I want to implement something similar to this into my app: play.google.com/store/apps/details?id=com.network.speedbooster
    – Unes
    Commented Jun 6 at 17:17
  • 1
    Android does not provide direct APIs for throttling network speed. You may consider using ThrottlingInterceptor and use it like OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new ThrottlingInterceptor(1024 * 100)) // Limit to 100 KBps .build();
    – Bob
    Commented Jun 7 at 9:48
  • @BobSmith However, this will restrict internet access only within the app. I want to limit internet speed across all apps while connected to the VPN
    – Unes
    Commented Jun 7 at 10:42
  • Create the VPN Service class and start it from activity as foreground service.
    – Bob
    Commented Jun 7 at 11:08

1 Answer 1

2
+300

Android does not provide direct APIs for throttling network speed. You may consider using ThrottlingInterceptor and use it like :

OkHttpClient client = new OkHttpClient.Builder()
  .addInterceptor(new ThrottlingInterceptor(1024 * 100)) // Limit to 100 KBps         
  .build();

TLDR;

. .. ...

This is a basic approach for handling bandwidth throttling :


1. Create the VPN Service Class

import android.app.PendingIntent;
import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;

public class MyVpnService extends VpnService implements Runnable {
    private Thread mThread;
    private ParcelFileDescriptor mInterface;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mThread != null) {
            mThread.interrupt();
        }
        mThread = new Thread(this, "MyVpnThread");
        mThread.start();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (mThread != null) {
            mThread.interrupt();
        }
        try {
            if (mInterface != null) {
                mInterface.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            // Configure the TUN interface
            Builder builder = new Builder();
            builder.addAddress("10.0.0.2", 24);
            builder.addRoute("0.0.0.0", 0);
            mInterface = builder.setSession("MyVPNService")
                    .setConfigureIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
                    .establish();

            // Packets to be sent are queued in this input stream.
            FileInputStream in = new FileInputStream(mInterface.getFileDescriptor());
            // Packets received need to be written to this output stream.
            FileOutputStream out = new FileOutputStream(mInterface.getFileDescriptor());

            // Allocate the buffer for a single packet.
            ByteBuffer packet = ByteBuffer.allocate(32767);

            while (true) {
                // Read the outgoing packet from the input stream.
                int length = in.read(packet.array());
                if (length > 0) {
                    // Process the packet
                    packet.limit(length);
                    processPacket(packet);

                    // Send the packet to the output stream.
                    out.write(packet.array(), 0, length);
                    packet.clear();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void processPacket(ByteBuffer packet) {
        // Desired bandwidth limit in bytes per second (100 KB/s)
        long bytesPerSecond = 102400; // 100 KBps

        // Calculate the time in nanoseconds that should elapse per byte
        long nanosPerByte = TimeUnit.SECONDS.toNanos(1) / bytesPerSecond;

        // The number of bytes in the packet
        int packetSize = packet.limit();

        // The expected time to send this packet
        long expectedTime = packetSize * nanosPerByte;

        // Introduce delay based on the expected time to throttle the speed
        try {
            Thread.sleep(TimeUnit.NANOSECONDS.toMillis(expectedTime));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Throttling interrupted", e);
        }
    }
}

2. Declate it in AndroidManifest.xml:

<service
    android:name=".MyVpnService"
    android:permission="android.permission.BIND_VPN_SERVICE">
    <intent-filter>
        <action android:name="android.net.VpnService" />
    </intent-filter>
</service>

3. Start and stop the VPN service from activity :

import android.content.Intent;
import android.net.VpnService;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final int VPN_REQUEST_CODE = 0x0F;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = VpnService.prepare(this);
        if (intent != null) {
            startActivityForResult(intent, VPN_REQUEST_CODE);
        } else {
            onActivityResult(VPN_REQUEST_CODE, RESULT_OK, null);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
            Intent intent = new Intent(this, MyVpnService.class);
            startService(intent);
        }
    }
}

Another Approach

1. As VPN Service (for deivce level interception)

a). Using ThrottlingInterceptor as a Service

class ThrottlingInterceptor(private val maxBytesPerSecond: Long) {

    private var bytesTransferred: Long = 0
    private var lastTime: Long = System.currentTimeMillis()

    @Synchronized
    fun shouldThrottle(packetSize: Int): Boolean {
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastTime > 1000) {
            lastTime = currentTime
            bytesTransferred = 0
        }

        if (bytesTransferred + packetSize > maxBytesPerSecond) {
            return true
        }

        bytesTransferred += packetSize
        return false
    }

    @Synchronized
    fun reset() {
        lastTime = System.currentTimeMillis()
        bytesTransferred = 0
    }
}

b). Your service class

class MyVpnService : VpnService() {
    
    private val interceptor = ThrottlingInterceptor(100 * 1024 / 8) // 100 kbps

    override fun onCreate() {
        super.onCreate()
        // Initialize the VPN service here
    }

    private fun processPacket(packet: ByteBuffer) {
        val packetSize = packet.remaining()

        if (interceptor.shouldThrottle(packetSize)) {
            try {
                Thread.sleep(1000) // Sleep for 1 second to reset the limit
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            interceptor.reset()
        }

        // Forward the packet
        forwardPacket(packet)
    }

    private fun forwardPacket(packet: ByteBuffer) {
        // Logic to forward packet to destination
    }
}

2. If need to restrict speed limit within your app

a). Interceptor class

import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.Buffer;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;

public class ThrottlingInterceptor implements Interceptor {

    private final long bytesPerSecond;

    public ThrottlingInterceptor(long bytesPerSecond) {
        this.bytesPerSecond = bytesPerSecond;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response originalResponse = chain.proceed(request);
        return originalResponse.newBuilder()
                .body(new ThrottlingResponseBody(originalResponse.body(), bytesPerSecond))
                .build();
    }

    private static class ThrottlingResponseBody extends okhttp3.ResponseBody {
        private final okhttp3.ResponseBody responseBody;
        private final long bytesPerSecond;
        private BufferedSource bufferedSource;

        public ThrottlingResponseBody(okhttp3.ResponseBody responseBody, long bytesPerSecond) {
            this.responseBody = responseBody;
            this.bytesPerSecond = bytesPerSecond;
        }

        @Override
        public okhttp3.MediaType contentType() {
            return responseBody.contentType();
        }

        @Override
        public long contentLength() {
            return responseBody.contentLength();
        }

        @Override
        public BufferedSource source() {
            if (bufferedSource == null) {
                bufferedSource = Okio.buffer(new ThrottlingSource(responseBody.source(), bytesPerSecond));
            }
            return bufferedSource;
        }

        private static class ThrottlingSource extends ForwardingSource {
            private final long bytesPerSecond;
            private long totalBytesRead = 0L;
            private long startTime = System.currentTimeMillis();

            ThrottlingSource(Source source, long bytesPerSecond) {
                super(source);
                this.bytesPerSecond = bytesPerSecond;
            }

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                if (bytesRead == -1) return -1;

                totalBytesRead += bytesRead;
                long elapsedTime = System.currentTimeMillis() - startTime;

                long expectedTime = (totalBytesRead * 1000) / bytesPerSecond;
                if (expectedTime > elapsedTime) {
                    try {
                        Thread.sleep(expectedTime - elapsedTime);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw new IOException("Throttling interrupted", e);
                    }
                }

                return bytesRead;
            }
        }
    }
}

b). Do this in Activity OkHttpClient fucntion

Use OkHttpClient with ThrottlingInterceptor while requesting network request

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new ThrottlingInterceptor(1024 * 100)) // Limit to 100 KBps
        .build();

Request request = new Request.Builder()
        .url("https://your.api.endpoint")
        .build();

try (Response response = client.newCall(request).execute()) {
    // Handle the response
} catch (IOException e) {
    e.printStackTrace();
}

References

Not the answer you're looking for? Browse other questions tagged or ask your own question.