diff --git a/ChangeLog b/ChangeLog
index d52260ab..89253f5b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,18 @@
# for this file format description,
# see https://github.com/olivierlacan/keep-a-changelog
+## [2.28.0] - 2019-08-27
+### Added
+- RAW datagrams in SAM
+- Publishing encrypted LeaseSet2 with DH or PSH authentication
+- Ability to disable battery optimization for Android
+- Transport Network ID Check
+### Changed
+- Set and handle published encrypted flag for LeaseSet2
+### Fixed
+- ReceiveID changes in the same stream
+- "\r\n" command terminator in SAM
+
## [2.27.0] - 2019-07-03
### Added
- Support of PSK and DH authentication for encrypted LeaseSet2
diff --git a/README.md b/README.md
index 540e3bed..b5fb8f7e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
-![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release)
-![GitHub](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)
+[![GitHub release](https://img.shields.io/github/release/PurpleI2P/i2pd.svg?label=latest%20release)](https://github.com/PurpleI2P/i2pd/releases/latest)
+[![Snapcraft release](https://snapcraft.io/i2pd/badge.svg)](https://snapcraft.io/i2pd)
+[![License](https://img.shields.io/github/license/PurpleI2P/i2pd.svg)](https://github.com/PurpleI2P/i2pd/blob/openssl/LICENSE)
i2pd
====
@@ -66,6 +67,7 @@ Build instructions:
* Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd)
* CentOS / Fedora / Mageia - [![Build Status](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/supervillain/i2pd/package/i2pd-git/)
* Docker image - [![Build Status](https://dockerbuildbadges.quelltext.eu/status.svg?organization=meeh&repository=i2pd)](https://hub.docker.com/r/meeh/i2pd/builds/)
+* Snap - [![Snap Status](https://build.snapcraft.io/badge/PurpleI2P/i2pd-snap.svg)](https://build.snapcraft.io/user/PurpleI2P/i2pd-snap)
* FreeBSD
* Android
* iOS
diff --git a/Win32/installer.iss b/Win32/installer.iss
index b00523da..4c78bb6a 100644
--- a/Win32/installer.iss
+++ b/Win32/installer.iss
@@ -1,5 +1,5 @@
#define I2Pd_AppName "i2pd"
-#define I2Pd_ver "2.27.0"
+#define I2Pd_ver "2.28.0"
#define I2Pd_Publisher "PurpleI2P"
[Setup]
diff --git a/android/.gitignore b/android/.gitignore
index 297d122f..666c6694 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -12,5 +12,5 @@ local.properties
build.sh
android.iml
build
-
-
+*.iml
+*.local
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 2ae14711..757b35bb 100755
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
-
-
+
+
+
+
+
+
+
diff --git a/android/res/values-ru/strings.xml b/android/res/values-ru/strings.xml
index d5dc58e8..1b0b2113 100755
--- a/android/res/values-ru/strings.xml
+++ b/android/res/values-ru/strings.xml
@@ -17,4 +17,12 @@
осталось
Запрос
Права для записи на SD карту отклонены, вам необходимо предоставить их для продолжения
+ Оптимизации аккумулятора
+ Оптимизации аккумулятора включены
+ Ваша версия Андроид не поддерживает отключение оптимизаций аккумулятора
+ Ваша операционная система осуществляет оптимизации расхода аккумулятора, которые могут приводить к выгрузке I2PD из памяти и прекращению его работы с целью сэкономить заряд аккумулятора.\nРекомендуется отключить эти оптимизации.
+ Ваша операционная система осуществляет оптимизации расхода аккумулятора, которые могут приводить к выгрузке I2PD из памяти и прекращению его работы с целью сэкономить заряд аккумулятора.\n\nВам сейчас будет предложено разрешить отключение этих оптимизаций.
+ Продолжить
+ Ваша версия Андроид не поддерживает показ диалога об оптимизациях аккумулятора для приложений.
+ Плановая остановка отменена
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index 3a5cf4fe..e08c1c46 100755
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -17,4 +17,12 @@
remaining
Prompt
SD card write permission denied, you need to allow this to continue
+ Battery optimizations enabled
+ Your Android is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\nIt is recommended to allow disabling those battery optimizations.
+ Your Android is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\n\nYou will now be asked to allow to disable those.
+ Continue
+ Your Android version does not support opting out of battery optimizations
+ Battery Optimizations
+ Your Android OS version does not support showing the dialog for battery optimizations for applications.
+ Planned shutdown canceled
diff --git a/android/src/org/purplei2p/i2pd/ForegroundService.java b/android/src/org/purplei2p/i2pd/ForegroundService.java
index 5c10e138..c1b1cc26 100644
--- a/android/src/org/purplei2p/i2pd/ForegroundService.java
+++ b/android/src/org/purplei2p/i2pd/ForegroundService.java
@@ -1,6 +1,5 @@
package org.purplei2p.i2pd;
-import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -11,10 +10,9 @@ import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationCompat;
+import androidx.annotation.RequiresApi;
+import androidx.core.app.NotificationCompat;
import android.util.Log;
-import android.widget.Toast;
public class ForegroundService extends Service {
private static final String TAG="FgService";
@@ -112,14 +110,15 @@ public class ForegroundService extends Service {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
- String channelId = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? createNotificationChannel() : "";
+ String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : "";
// Set the info for the views that show in the notification panel.
- Notification notification = new NotificationCompat.Builder(this, channelId)
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setOngoing(true)
- .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon
- .setPriority(Notification.PRIORITY_DEFAULT)
- .setCategory(Notification.CATEGORY_SERVICE)
+ .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon
+ if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT);
+ if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE);
+ Notification notification = builder
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.app_name)) // the label of the entry
@@ -141,9 +140,10 @@ public class ForegroundService extends Service {
//chan.setLightColor(Color.PURPLE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
- service.createNotificationChannel(chan);
+ if(service!=null)service.createNotificationChannel(chan);
+ else Log.e(TAG, "error: NOTIFICATION_SERVICE is null");
return channelId;
}
- private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
+ private static final DaemonSingleton daemon = DaemonSingleton.getInstance();
}
diff --git a/android/src/org/purplei2p/i2pd/I2PDActivity.java b/android/src/org/purplei2p/i2pd/I2PDActivity.java
index b5f85c5b..89881b42 100755
--- a/android/src/org/purplei2p/i2pd/I2PDActivity.java
+++ b/android/src/org/purplei2p/i2pd/I2PDActivity.java
@@ -14,32 +14,44 @@ import java.util.Timer;
import java.util.TimerTask;
import android.Manifest;
+import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
+import android.os.PowerManager;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
// For future package update checking
-import org.purplei2p.i2pd.BuildConfig;
+
+import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS;
public class I2PDActivity extends Activity {
private static final String TAG = "i2pdActvt";
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000;
+ public static final String PACKAGE_URI_SCHEME = "package:";
private TextView textView;
private boolean assetsCopied;
@@ -53,32 +65,27 @@ public class I2PDActivity extends Activity {
public void daemonStateUpdate()
{
processAssets();
- runOnUiThread(new Runnable(){
-
- @Override
- public void run() {
- try {
- if(textView==null) return;
- Throwable tr = daemon.getLastThrowable();
- if(tr!=null) {
- textView.setText(throwableToString(tr));
- return;
- }
- DaemonSingleton.State state = daemon.getState();
- textView.setText(
- String.valueOf(getText(state.getStatusStringResourceId()))+
- (DaemonSingleton.State.startFailed.equals(state) ? ": "+daemon.getDaemonStartResult() : "")+
- (DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? ": "+formatGraceTimeRemaining()+" "+getText(R.string.remaining) : "")
- );
- } catch (Throwable tr) {
- Log.e(TAG,"error ignored",tr);
- }
- }
- });
+ runOnUiThread(() -> {
+ try {
+ if(textView==null) return;
+ Throwable tr = daemon.getLastThrowable();
+ if(tr!=null) {
+ textView.setText(throwableToString(tr));
+ return;
+ }
+ DaemonSingleton.State state = daemon.getState();
+ String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : "";
+ String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : "";
+ textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr));
+ } catch (Throwable tr) {
+ Log.e(TAG,"error ignored",tr);
+ }
+ });
}
};
private static volatile long graceStartedMillis;
private static final Object graceStartedMillis_LOCK=new Object();
+ private Menu optionsMenu;
private static String formatGraceTimeRemaining() {
long remainingSeconds;
@@ -92,6 +99,7 @@ public class I2PDActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
textView = new TextView(this);
@@ -121,6 +129,8 @@ public class I2PDActivity extends Activity {
}
rescheduleGraceStop(gracefulQuitTimer, gracefulStopAtMillis);
}
+
+ openBatteryOptimizationDialogIfNeeded();
}
@Override
@@ -128,7 +138,7 @@ public class I2PDActivity extends Activity {
super.onDestroy();
textView = null;
daemon.removeStateChangeListener(daemonStateUpdatedListener);
- //cancelGracefulStop();
+ //cancelGracefulStop0();
try{
doUnbindService();
}catch(Throwable tr){
@@ -137,24 +147,20 @@ public class I2PDActivity extends Activity {
}
@Override
- public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
- switch (requestCode)
- {
- case MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE:
- {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
- Log.e(TAG, "Memory permission granted");
- else
- Log.e(TAG, "Memory permission declined");
- // TODO: terminate
- return;
- }
- default: ;
- }
+ if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
+ Log.e(TAG, "WR_EXT_STORAGE perm granted");
+ else {
+ Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd");
+ i2pdStop();
+ //TODO must work w/o this perm, ask orignal
+ }
+ }
}
- private static void cancelGracefulStop() {
+ private void cancelGracefulStop0() {
Timer gracefulQuitTimer = getGracefulQuitTimer();
if(gracefulQuitTimer!=null) {
gracefulQuitTimer.cancel();
@@ -225,11 +231,17 @@ public class I2PDActivity extends Activity {
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.options_main, menu);
+ menu.findItem(R.id.action_battery_otimizations).setVisible(isBatteryOptimizationsOpenOsDialogApiAvailable());
+ this.optionsMenu = menu;
return true;
}
+ private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() {
+ return android.os.Build.VERSION.SDK_INT >= 23;
+ }
+
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
@@ -240,37 +252,43 @@ public class I2PDActivity extends Activity {
i2pdStop();
return true;
case R.id.action_graceful_stop:
- if (getGracefulQuitTimer()!= null)
- {
- item.setTitle(R.string.action_graceful_stop);
- i2pdCancelGracefulStop ();
+ synchronized (graceStartedMillis_LOCK) {
+ if (getGracefulQuitTimer() != null)
+ cancelGracefulStop();
+ else
+ i2pdGracefulStop();
}
- else
- {
- item.setTitle(R.string.action_cancel_graceful_stop);
- i2pdGracefulStop();
- }
- return true;
+ return true;
+ case R.id.action_battery_otimizations:
+ onActionBatteryOptimizations();
+ return true;
}
return super.onOptionsItemSelected(item);
}
- private void i2pdStop() {
- cancelGracefulStop();
- new Thread(new Runnable(){
-
- @Override
- public void run() {
- Log.d(TAG, "stopping");
- try{
- daemon.stopDaemon();
- }catch (Throwable tr) {
- Log.e(TAG, "", tr);
- }
+ private void onActionBatteryOptimizations() {
+ if (isBatteryOptimizationsOpenOsDialogApiAvailable()) {
+ try {
+ startActivity(new Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG,"BATT_OPTIM_DIALOG_ActvtNotFound", e);
+ Toast.makeText(this, R.string.os_version_does_not_support_battery_optimizations_show_os_dialog_api, Toast.LENGTH_SHORT).show();
}
+ }
+ }
- },"stop").start();
+ private void i2pdStop() {
+ cancelGracefulStop0();
+ new Thread(() -> {
+ Log.d(TAG, "stopping");
+ try {
+ daemon.stopDaemon();
+ } catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ quit(); //TODO make menu items for starting i2pd. On my Android, I need to reboot the OS to restart i2pd.
+ },"stop").start();
}
private static volatile Timer gracefulQuitTimer;
@@ -288,55 +306,45 @@ public class I2PDActivity extends Activity {
}
Toast.makeText(this, R.string.graceful_stop_is_in_progress,
Toast.LENGTH_SHORT).show();
- new Thread(new Runnable(){
-
- @Override
- public void run() {
- try {
- Log.d(TAG, "grac stopping");
- if(daemon.isStartedOkay()) {
- daemon.stopAcceptingTunnels();
- long gracefulStopAtMillis;
- synchronized (graceStartedMillis_LOCK) {
- graceStartedMillis = System.currentTimeMillis();
- gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS;
- }
- rescheduleGraceStop(null,gracefulStopAtMillis);
- } else {
- i2pdStop();
- }
- } catch(Throwable tr) {
- Log.e(TAG,"",tr);
- }
- }
-
- },"gracInit").start();
+ new Thread(() -> {
+ try {
+ Log.d(TAG, "grac stopping");
+ if(daemon.isStartedOkay()) {
+ daemon.stopAcceptingTunnels();
+ long gracefulStopAtMillis;
+ synchronized (graceStartedMillis_LOCK) {
+ graceStartedMillis = System.currentTimeMillis();
+ gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS;
+ }
+ rescheduleGraceStop(null,gracefulStopAtMillis);
+ } else {
+ i2pdStop();
+ }
+ } catch(Throwable tr) {
+ Log.e(TAG,"",tr);
+ }
+ },"gracInit").start();
}
- private void i2pdCancelGracefulStop()
+ private void cancelGracefulStop()
{
- cancelGracefulStop();
- Toast.makeText(this, R.string.startedOkay, Toast.LENGTH_SHORT).show();
- new Thread(new Runnable()
- {
- @Override
- public void run()
- {
- try
- {
- Log.d(TAG, "grac stopping cancel");
- if(daemon.isStartedOkay())
- daemon.startAcceptingTunnels();
- else
- i2pdStop();
+ cancelGracefulStop0();
+ new Thread(() -> {
+ try
+ {
+ Log.d(TAG, "canceling grac stop");
+ if(daemon.isStartedOkay()) {
+ daemon.startAcceptingTunnels();
+ runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show());
}
- catch(Throwable tr)
- {
- Log.e(TAG,"",tr);
- }
- }
-
- },"gracCancel").start();
+ else
+ i2pdStop();
+ }
+ catch(Throwable tr)
+ {
+ Log.e(TAG,"",tr);
+ }
+ },"gracCancel").start();
}
private void rescheduleGraceStop(Timer gracefulQuitTimerOld, long gracefulStopAtMillis) {
@@ -364,8 +372,19 @@ public class I2PDActivity extends Activity {
return gracefulQuitTimer;
}
- private static void setGracefulQuitTimer(Timer gracefulQuitTimer) {
+ private void setGracefulQuitTimer(Timer gracefulQuitTimer) {
I2PDActivity.gracefulQuitTimer = gracefulQuitTimer;
+ runOnUiThread(()-> {
+ Menu menu = optionsMenu;
+ if (menu != null) {
+ MenuItem item = menu.findItem(R.id.action_graceful_stop);
+ if (item != null) {
+ synchronized (graceStartedMillis_LOCK) {
+ item.setTitle(getGracefulQuitTimer() != null ? R.string.action_cancel_graceful_stop : R.string.action_graceful_stop);
+ }
+ }
+ }
+ });
}
/**
@@ -388,19 +407,22 @@ public class I2PDActivity extends Activity {
// to a file. That doesn't appear to be the case. If the returned array is
// null or has 0 length, we assume the path is to a file. This means empty
// directories will get turned into files.
- if (contents == null || contents.length == 0)
- throw new IOException();
+ if (contents == null || contents.length == 0) {
+ copyFileAsset(path);
+ return;
+ }
// Make the directory.
File dir = new File(i2pdpath, path);
- dir.mkdirs();
+ boolean result = dir.mkdirs();
+ Log.d(TAG, "dir.mkdirs() returned " + result);
// Recurse on the contents.
for (String entry : contents) {
- copyAsset(path + "/" + entry);
+ copyAsset(path + '/' + entry);
}
} catch (IOException e) {
- copyFileAsset(path);
+ Log.e(TAG, "ex ignored for path='" + path + "'", e);
}
}
@@ -413,63 +435,89 @@ public class I2PDActivity extends Activity {
*/
private void copyFileAsset(String path) {
File file = new File(i2pdpath, path);
- if(!file.exists()) try {
- InputStream in = getAssets().open(path);
- OutputStream out = new FileOutputStream(file);
- byte[] buffer = new byte[1024];
- int read = in.read(buffer);
- while (read != -1) {
- out.write(buffer, 0, read);
- read = in.read(buffer);
+ if(!file.exists()) {
+ try {
+ try (InputStream in = getAssets().open(path) ) {
+ try (OutputStream out = new FileOutputStream(file)) {
+ byte[] buffer = new byte[1024];
+ int read = in.read(buffer);
+ while (read != -1) {
+ out.write(buffer, 0, read);
+ read = in.read(buffer);
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "", e);
}
- out.close();
- in.close();
- } catch (IOException e) {
- Log.e(TAG, "", e);
}
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
- for (File child : fileOrDirectory.listFiles()) {
- deleteRecursive(child);
+ File[] files = fileOrDirectory.listFiles();
+ if(files!=null) {
+ for (File child : files) {
+ deleteRecursive(child);
+ }
}
}
- fileOrDirectory.delete();
+ boolean deleteResult = fileOrDirectory.delete();
+ if(!deleteResult)Log.e(TAG, "fileOrDirectory.delete() returned "+deleteResult+", absolute path='"+fileOrDirectory.getAbsolutePath()+"'");
}
private void processAssets() {
if (!assetsCopied) try {
assetsCopied = true; // prevent from running on every state update
- File holderfile = new File(i2pdpath, "assets.ready");
+ File holderFile = new File(i2pdpath, "assets.ready");
String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder();
- if (holderfile.exists()) try { // if holder file exists, read assets version string
- BufferedReader br = new BufferedReader(new FileReader(holderfile));
- String line;
+ if (holderFile.exists()) {
+ try { // if holder file exists, read assets version string
+ FileReader fileReader = new FileReader(holderFile);
- while ((line = br.readLine()) != null) {
- text.append(line);
- }
- br.close();
- }
- catch (IOException e) {
- Log.e(TAG, "", e);
- }
+ try {
+ BufferedReader br = new BufferedReader(fileReader);
+
+ try {
+ String line;
+
+ while ((line = br.readLine()) != null) {
+ text.append(line);
+ }
+ }finally {
+ try{
+ br.close();
+ } catch (IOException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ } finally {
+ try{
+ fileReader.close();
+ } catch (IOException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "", e);
+ }
+ }
// if version differs from current app version or null, try to delete certificates folder
if (!text.toString().contains(versionName)) try {
- holderfile.delete();
- File certpath = new File(i2pdpath, "certificates");
- deleteRecursive(certpath);
+ boolean deleteResult = holderFile.delete();
+ if(!deleteResult)Log.e(TAG, "holderFile.delete() returned "+deleteResult+", absolute path='"+holderFile.getAbsolutePath()+"'");
+ File certPath = new File(i2pdpath, "certificates");
+ deleteRecursive(certPath);
}
catch (Throwable tr) {
Log.e(TAG, "", tr);
}
- // copy assets. If processed file exists, it won't be overwrited
+ // copy assets. If processed file exists, it won't be overwritten
copyAsset("addressbook");
copyAsset("certificates");
copyAsset("tunnels.d");
@@ -478,14 +526,95 @@ public class I2PDActivity extends Activity {
copyAsset("tunnels.conf");
// update holder file about successful copying
- FileWriter writer = new FileWriter(holderfile);
- writer.append(versionName);
- writer.flush();
- writer.close();
+ FileWriter writer = new FileWriter(holderFile);
+ try {
+ writer.append(versionName);
+ } finally {
+ try{
+ writer.close();
+ }catch (IOException e){
+ Log.e(TAG,"on writer close", e);
+ }
+ }
}
catch (Throwable tr)
{
- Log.e(TAG,"copy assets",tr);
+ Log.e(TAG,"on assets copying", tr);
}
}
+
+ @SuppressLint("BatteryLife")
+ private void openBatteryOptimizationDialogIfNeeded() {
+ boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true);
+ Log.i(TAG,"BATT_OPTIM_questionEnabled=="+questionEnabled);
+ if (!isKnownIgnoringBatteryOptimizations()
+ && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M
+ && questionEnabled) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.battery_optimizations_enabled);
+ builder.setMessage(R.string.battery_optimizations_enabled_dialog);
+ builder.setPositiveButton(R.string.continue_str, (dialog, which) -> {
+ try {
+ startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse(PACKAGE_URI_SCHEME + getPackageName())));
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG,"BATT_OPTIM_ActvtNotFound", e);
+ Toast.makeText(this, R.string.device_does_not_support_disabling_battery_optimizations, Toast.LENGTH_SHORT).show();
+ }
+ });
+ builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
+ final AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.show();
+ }
+ }
+
+ private void setNeverAskForBatteryOptimizationsAgain() {
+ getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
+ }
+
+ protected boolean isKnownIgnoringBatteryOptimizations() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ if (pm == null) {
+ Log.i(TAG, "BATT_OPTIM: POWER_SERVICE==null");
+ return false;
+ }
+ boolean ignoring = pm.isIgnoringBatteryOptimizations(getPackageName());
+ Log.i(TAG, "BATT_OPTIM: ignoring==" + ignoring);
+ return ignoring;
+ } else {
+ Log.i(TAG, "BATT_OPTIM: old sdk version=="+Build.VERSION.SDK_INT);
+ return false;
+ }
+ }
+
+ protected SharedPreferences getPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ }
+
+ private String getBatteryOptimizationPreferenceKey() {
+ @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
+ return "show_battery_optimization" + (device == null ? "" : device);
+ }
+
+ private void quit() {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ finishAndRemoveTask();
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ finishAffinity();
+ } else {
+ //moveTaskToBack(true);
+ finish();
+ }
+ }catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ try{
+ daemon.stopDaemon();
+ }catch (Throwable tr) {
+ Log.e(TAG, "", tr);
+ }
+ System.exit(0);
+ }
}
diff --git a/appveyor.yml b/appveyor.yml
index dfbc656c..67693ac3 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.27.0.{build}
+version: 2.28.0.{build}
pull_requests:
do_not_increment_build_number: true
branches:
diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile
index e0c4d26e..322261b8 100644
--- a/contrib/docker/Dockerfile
+++ b/contrib/docker/Dockerfile
@@ -36,8 +36,8 @@ RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool zlib
&& cd /usr/local/bin \
&& strip i2pd \
&& rm -fr /tmp/build && apk --no-cache --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \
- boost-python3 python3 gdbm boost-unit_test_framework boost-python linux-headers boost-prg_exec_monitor \
- boost-serialization boost-signals boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \
+ boost-python3 python3 gdbm boost-unit_test_framework linux-headers boost-prg_exec_monitor \
+ boost-serialization boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \
libtool g++ gcc pkgconfig
# 2. Adding required libraries to run i2pd to ensure it will run.
diff --git a/contrib/rpm/i2pd-git.spec b/contrib/rpm/i2pd-git.spec
index 1dda44b1..ed4bad86 100644
--- a/contrib/rpm/i2pd-git.spec
+++ b/contrib/rpm/i2pd-git.spec
@@ -1,7 +1,7 @@
%define git_hash %(git rev-parse HEAD | cut -c -7)
Name: i2pd-git
-Version: 2.27.0
+Version: 2.28.0
Release: git%{git_hash}%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd
@@ -110,6 +110,9 @@ getent passwd i2pd >/dev/null || \
%changelog
+* Tue Aug 27 2019 orignal - 2.28.0
+- update to 2.28.0
+
* Wed Jul 3 2019 orignal - 2.27.0
- update to 2.27.0
diff --git a/contrib/rpm/i2pd.spec b/contrib/rpm/i2pd.spec
index 67c7f4bf..61271680 100644
--- a/contrib/rpm/i2pd.spec
+++ b/contrib/rpm/i2pd.spec
@@ -1,5 +1,5 @@
Name: i2pd
-Version: 2.27.0
+Version: 2.28.0
Release: 1%{?dist}
Summary: I2P router written in C++
Conflicts: i2pd-git
@@ -108,6 +108,9 @@ getent passwd i2pd >/dev/null || \
%changelog
+* Tue Aug 27 2019 orignal - 2.28.0
+- update to 2.28.0
+
* Wed Jul 3 2019 orignal - 2.27.0
- update to 2.27.0
diff --git a/debian/changelog b/debian/changelog
index 0f30a066..4a6fbfd8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+i2pd (2.28.0-1) unstable; urgency=medium
+
+ * updated to version 2.28.0/0.9.42
+
+ -- orignal Tue, 27 Aug 2019 16:00:00 +0000
+
i2pd (2.27.0-1) unstable; urgency=medium
* updated to version 2.27.0/0.9.41
diff --git a/libi2pd/Blinding.cpp b/libi2pd/Blinding.cpp
index 14852340..e51c0fcc 100644
--- a/libi2pd/Blinding.cpp
+++ b/libi2pd/Blinding.cpp
@@ -139,6 +139,11 @@ namespace data
{
uint8_t addr[40]; // TODO: define length from b33
size_t l = i2p::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
+ if (l < 32)
+ {
+ LogPrint (eLogError, "Blinding: malformed b33 ", b33);
+ return;
+ }
uint32_t checksum = crc32 (0, addr + 3, l - 3);
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp
index ed87a054..7e04fce8 100644
--- a/libi2pd/Datagram.cpp
+++ b/libi2pd/Datagram.cpp
@@ -12,10 +12,8 @@ namespace i2p
namespace datagram
{
DatagramDestination::DatagramDestination (std::shared_ptr owner):
- m_Owner (owner.get()),
- m_Receiver (nullptr)
+ m_Owner (owner), m_Receiver (nullptr), m_RawReceiver (nullptr)
{
- m_Identity.FromBase64 (owner->GetIdentity()->ToBase64());
}
DatagramDestination::~DatagramDestination ()
@@ -28,14 +26,15 @@ namespace datagram
auto owner = m_Owner;
std::vector v(MAX_DATAGRAM_SIZE);
uint8_t * buf = v.data();
- auto identityLen = m_Identity.ToBuffer (buf, MAX_DATAGRAM_SIZE);
+ auto localIdentity = m_Owner->GetIdentity ();
+ auto identityLen = localIdentity->ToBuffer (buf, MAX_DATAGRAM_SIZE);
uint8_t * signature = buf + identityLen;
- auto signatureLen = m_Identity.GetSignatureLen ();
+ auto signatureLen = localIdentity->GetSignatureLen ();
uint8_t * buf1 = signature + signatureLen;
size_t headerLen = identityLen + signatureLen;
memcpy (buf1, payload, len);
- if (m_Identity.GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
+ if (localIdentity->GetSigningKeyType () == i2p::data::SIGNING_KEY_TYPE_DSA_SHA1)
{
uint8_t hash[32];
SHA256(buf1, len, hash);
@@ -49,7 +48,13 @@ namespace datagram
session->SendMsg(msg);
}
-
+ void DatagramDestination::SendRawDatagramTo(const uint8_t * payload, size_t len, const i2p::data::IdentHash & identity, uint16_t fromPort, uint16_t toPort)
+ {
+ auto msg = CreateDataMessage (payload, len, fromPort, toPort, true); // raw
+ auto session = ObtainSession(identity);
+ session->SendMsg(msg);
+ }
+
void DatagramDestination::HandleDatagram (uint16_t fromPort, uint16_t toPort,uint8_t * const &buf, size_t len)
{
i2p::data::IdentityEx identity;
@@ -82,6 +87,14 @@ namespace datagram
LogPrint (eLogWarning, "Datagram signature verification failed");
}
+ void DatagramDestination::HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
+ {
+ if (m_RawReceiver)
+ m_RawReceiver (fromPort, toPort, buf, len);
+ else
+ LogPrint (eLogWarning, "DatagramDestination: no receiver for raw datagram");
+ }
+
DatagramDestination::Receiver DatagramDestination::FindReceiver(uint16_t port)
{
std::lock_guard lock(m_ReceiversMutex);
@@ -92,18 +105,24 @@ namespace datagram
return r;
}
- void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
+ void DatagramDestination::HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw)
{
// unzip it
uint8_t uncompressed[MAX_DATAGRAM_SIZE];
size_t uncompressedLen = m_Inflator.Inflate (buf, len, uncompressed, MAX_DATAGRAM_SIZE);
if (uncompressedLen)
- HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
+ {
+ if (isRaw)
+ HandleRawDatagram (fromPort, toPort, uncompressed, uncompressedLen);
+ else
+ HandleDatagram (fromPort, toPort, uncompressed, uncompressedLen);
+ }
else
LogPrint (eLogWarning, "Datagram: decompression failed");
}
- std::shared_ptr DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
+
+ std::shared_ptr DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort, bool isRaw)
{
auto msg = NewI2NPMessage ();
uint8_t * buf = msg->GetPayload ();
@@ -114,7 +133,7 @@ namespace datagram
htobe32buf (msg->GetPayload (), size); // length
htobe16buf (buf + 4, fromPort); // source port
htobe16buf (buf + 6, toPort); // destination port
- buf[9] = i2p::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
+ buf[9] = isRaw ? i2p::client::PROTOCOL_TYPE_RAW : i2p::client::PROTOCOL_TYPE_DATAGRAM; // raw or datagram protocol
msg->len += size + 4;
msg->FillI2NPMessageHeader (eI2NPData);
}
@@ -170,7 +189,7 @@ namespace datagram
return nullptr;
}
- DatagramSession::DatagramSession(i2p::client::ClientDestination * localDestination,
+ DatagramSession::DatagramSession(std::shared_ptr localDestination,
const i2p::data::IdentHash & remoteIdent) :
m_LocalDestination(localDestination),
m_RemoteIdent(remoteIdent),
diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h
index 039417ea..0cfec838 100644
--- a/libi2pd/Datagram.h
+++ b/libi2pd/Datagram.h
@@ -37,7 +37,7 @@ namespace datagram
class DatagramSession : public std::enable_shared_from_this
{
public:
- DatagramSession(i2p::client::ClientDestination * localDestination, const i2p::data::IdentHash & remoteIdent);
+ DatagramSession(std::shared_ptr localDestination, const i2p::data::IdentHash & remoteIdent);
void Start ();
void Stop ();
@@ -81,7 +81,7 @@ namespace datagram
void HandleLeaseSetUpdated(std::shared_ptr ls);
private:
- i2p::client::ClientDestination * m_LocalDestination;
+ std::shared_ptr m_LocalDestination;
i2p::data::IdentHash m_RemoteIdent;
std::shared_ptr m_RemoteLeaseSet;
std::shared_ptr m_RoutingSession;
@@ -99,22 +99,28 @@ namespace datagram
class DatagramDestination
{
typedef std::function Receiver;
+ typedef std::function RawReceiver;
+
public:
DatagramDestination (std::shared_ptr owner);
~DatagramDestination ();
- void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
- void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
-
+ void SendDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
+ void SendRawDatagramTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash & ident, uint16_t fromPort = 0, uint16_t toPort = 0);
+ void HandleDataMessagePayload (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len, bool isRaw = false);
+
void SetReceiver (const Receiver& receiver) { m_Receiver = receiver; };
void ResetReceiver () { m_Receiver = nullptr; };
void SetReceiver (const Receiver& receiver, uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts[port] = receiver; };
void ResetReceiver (uint16_t port) { std::lock_guard lock(m_ReceiversMutex); m_ReceiversByPorts.erase (port); };
+ void SetRawReceiver (const RawReceiver& receiver) { m_RawReceiver = receiver; };
+ void ResetRawReceiver () { m_RawReceiver = nullptr; };
+
std::shared_ptr GetInfoForRemote(const i2p::data::IdentHash & remote);
// clean up stale sessions
@@ -124,17 +130,19 @@ namespace datagram
std::shared_ptr ObtainSession(const i2p::data::IdentHash & ident);
- std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort);
+ std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort, bool isRaw = false);
void HandleDatagram (uint16_t fromPort, uint16_t toPort, uint8_t *const& buf, size_t len);
-
+ void HandleRawDatagram (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
+
/** find a receiver by port, if none by port is found try default receiever, otherwise returns nullptr */
Receiver FindReceiver(uint16_t port);
private:
- i2p::client::ClientDestination * m_Owner;
- i2p::data::IdentityEx m_Identity;
+
+ std::shared_ptr m_Owner;
Receiver m_Receiver; // default
+ RawReceiver m_RawReceiver; // default
std::mutex m_SessionsMutex;
std::map m_Sessions;
std::mutex m_ReceiversMutex;
diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp
index 5187fedc..34ff374f 100644
--- a/libi2pd/Destination.cpp
+++ b/libi2pd/Destination.cpp
@@ -846,7 +846,7 @@ namespace client
ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params):
LeaseSetDestination (isPublic, params), m_Keys (keys), m_StreamingAckDelay (DEFAULT_INITIAL_ACK_DELAY),
m_DatagramDestination (nullptr), m_RefCounter (0),
- m_ReadyChecker(GetService())
+ m_ReadyChecker(GetService()), m_AuthType (i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_NONE)
{
if (keys.IsOfflineSignature () && GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_LEASESET)
SetLeaseSetType (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2); // offline keys can be published with LS2 only
@@ -868,12 +868,46 @@ namespace client
if (isPublic)
LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created");
- // extract streaming params
- if (params)
+ try
+ {
+ if (params)
+ {
+ // extract streaming params
+ auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY);
+ if (it != params->end ())
+ m_StreamingAckDelay = std::stoi(it->second);
+
+ if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
+ {
+ // authentication for encrypted LeaseSet
+ it = params->find (I2CP_PARAM_LEASESET_AUTH_TYPE);
+ m_AuthType = std::stoi (it->second);
+ if (m_AuthType > 0)
+ {
+ m_AuthKeys = std::make_shared >();
+ if (m_AuthType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_DH)
+ ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_DH, params);
+ else if (m_AuthType == i2p::data::ENCRYPTED_LEASESET_AUTH_TYPE_PSK)
+ ReadAuthKey (I2CP_PARAM_LEASESET_CLIENT_PSK, params);
+ else
+ {
+ LogPrint (eLogError, "Destination: Unexpected auth type ", m_AuthType);
+ m_AuthType = 0;
+ }
+ if (m_AuthKeys->size ())
+ LogPrint (eLogInfo, "Destination: ", m_AuthKeys->size (), " auth keys read");
+ else
+ {
+ LogPrint (eLogError, "Destination: No auth keys read for auth type ", m_AuthType);
+ m_AuthKeys = nullptr;
+ }
+ }
+ }
+ }
+ }
+ catch (std::exception & ex)
{
- auto it = params->find (I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY);
- if (it != params->end ())
- m_StreamingAckDelay = std::stoi(it->second);
+ LogPrint(eLogError, "Destination: unable to parse parameters for destination: ", ex.what());
}
}
@@ -977,6 +1011,13 @@ namespace client
else
LogPrint (eLogError, "Destination: Missing datagram destination");
break;
+ case PROTOCOL_TYPE_RAW:
+ // raw datagram
+ if (m_DatagramDestination)
+ m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length, true);
+ else
+ LogPrint (eLogError, "Destination: Missing raw datagram destination");
+ break;
default:
LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]);
}
@@ -1139,10 +1180,11 @@ namespace client
{
// standard LS2 (type 3) first
auto keyLen = m_Decryptor ? m_Decryptor->GetPublicKeyLen () : 256;
+ bool isPublishedEncrypted = GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
auto ls2 = std::make_shared (i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2,
- m_Keys, m_EncryptionKeyType, keyLen, m_EncryptionPublicKey, tunnels);
- if (GetLeaseSetType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2) // encrypt if type 5
- ls2 = std::make_shared (ls2, m_Keys);
+ m_Keys, m_EncryptionKeyType, keyLen, m_EncryptionPublicKey, tunnels, IsPublic (), isPublishedEncrypted);
+ if (isPublishedEncrypted) // encrypt if type 5
+ ls2 = std::make_shared (ls2, m_Keys, m_AuthType, m_AuthKeys);
leaseSet = ls2;
}
SetLeaseSet (leaseSet);
@@ -1161,5 +1203,22 @@ namespace client
LogPrint (eLogError, "Destinations: decryptor is not set");
return false;
}
+
+ void ClientDestination::ReadAuthKey (const std::string& group, const std::map * params)
+ {
+ for (auto it: *params)
+ if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group))
+ {
+ auto pos = it.second.find (':');
+ if (pos != std::string::npos)
+ {
+ i2p::data::AuthPublicKey pubKey;
+ if (pubKey.FromBase64 (it.second.substr (pos+1)))
+ m_AuthKeys->push_back (pubKey);
+ else
+ LogPrint (eLogError, "Destination: Unexpected auth key ", it.second.substr (pos+1));
+ }
+ }
+ }
}
}
diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h
index 35a9dbae..148a6101 100644
--- a/libi2pd/Destination.h
+++ b/libi2pd/Destination.h
@@ -56,7 +56,10 @@ namespace client
const int DEFAULT_LEASESET_TYPE = 1;
const char I2CP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "i2cp.leaseSetEncType";
const char I2CP_PARAM_LEASESET_PRIV_KEY[] = "i2cp.leaseSetPrivKey"; // PSK decryption key, base64
-
+ const char I2CP_PARAM_LEASESET_AUTH_TYPE[] = "i2cp.leaseSetAuthType";
+ const char I2CP_PARAM_LEASESET_CLIENT_DH[] = "i2cp.leaseSetClient.dh"; // group of i2cp.leaseSetClient.dh.nnn
+ const char I2CP_PARAM_LEASESET_CLIENT_PSK[] = "i2cp.leaseSetClient.psk"; // group of i2cp.leaseSetClient.psk.nnn
+
// latency
const char I2CP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min";
const int DEFAULT_MIN_TUNNEL_LATENCY = 0;
@@ -131,6 +134,7 @@ namespace client
void SetLeaseSet (std::shared_ptr newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; };
void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
+ bool IsPublic () const { return m_IsPublic; };
virtual void CleanupDestination () {}; // additional clean up in derived classes
// I2CP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
@@ -248,6 +252,9 @@ namespace client
void ScheduleCheckForReady(ReadyPromise * p);
void HandleCheckForReady(const boost::system::error_code & ecode, ReadyPromise * p);
#endif
+
+ void ReadAuthKey (const std::string& group, const std::map * params);
+
private:
i2p::data::PrivateKeys m_Keys;
@@ -263,6 +270,9 @@ namespace client
boost::asio::deadline_timer m_ReadyChecker;
+ int m_AuthType;
+ std::shared_ptr > m_AuthKeys;
+
public:
// for HTTP only
diff --git a/libi2pd/LeaseSet.cpp b/libi2pd/LeaseSet.cpp
index 69a29377..b516f010 100644
--- a/libi2pd/LeaseSet.cpp
+++ b/libi2pd/LeaseSet.cpp
@@ -252,7 +252,7 @@ namespace data
}
LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases):
- LeaseSet (storeLeases), m_StoreType (storeType), m_OrigStoreType (storeType)
+ LeaseSet (storeLeases), m_StoreType (storeType)
{
SetBuffer (buf, len);
if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
@@ -262,7 +262,7 @@ namespace data
}
LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret):
- LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_OrigStoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
+ LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
ReadFromBufferEncrypted (buf, len, key, secret);
}
@@ -302,6 +302,12 @@ namespace data
return;
}
}
+ if (flags & LEASESET2_FLAG_UNPUBLISHED_LEASESET) m_IsPublic = false;
+ if (flags & LEASESET2_FLAG_PUBLISHED_ENCRYPTED)
+ {
+ m_IsPublishedEncrypted = true;
+ m_IsPublic = true;
+ }
// type specific part
size_t s = 0;
switch (m_StoreType)
@@ -741,7 +747,8 @@ namespace data
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey,
- std::vector > tunnels):
+ std::vector > tunnels,
+ bool isPublic, bool isPublishedEncrypted):
LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{
auto identity = keys.GetPublic ();
@@ -756,6 +763,12 @@ namespace data
flags |= LEASESET2_FLAG_OFFLINE_KEYS;
m_BufferLen += keys.GetOfflineSignature ().size ();
}
+ if (isPublishedEncrypted)
+ {
+ flags |= LEASESET2_FLAG_PUBLISHED_ENCRYPTED;
+ isPublic = true;
+ }
+ if (!isPublic) flags |= LEASESET2_FLAG_UNPUBLISHED_LEASESET;
m_Buffer = new uint8_t[m_BufferLen + 1];
m_Buffer[0] = storeType;
@@ -809,12 +822,22 @@ namespace data
m_Buffer[0] = storeType;
}
- LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, i2p::data::SigningKeyType blindedKeyType):
+ LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys,
+ int authType, std::shared_ptr > authKeys):
LocalLeaseSet2 (ls->GetIdentity ()), m_InnerLeaseSet (ls)
{
- size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1,
- lenOuterCiphertext = lenOuterPlaintext + 32;
- m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/;
+ size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1;
+ uint8_t layer1Flags = 0;
+ if (authKeys)
+ {
+ if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH) layer1Flags |= 0x01; // DH, authentication scheme 0, auth bit 1
+ else if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_PSK) layer1Flags |= 0x03; // PSK, authentication scheme 1, auth bit 1
+ if (layer1Flags)
+ lenOuterPlaintext += 32 + 2 + authKeys->size ()*40; // auth data len
+ }
+ size_t lenOuterCiphertext = lenOuterPlaintext + 32;
+
+ m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/;
m_Buffer = new uint8_t[m_BufferLen + 1];
m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
BlindedPublicKey blindedKey (ls->GetIdentity ());
@@ -823,9 +846,9 @@ namespace data
i2p::util::GetDateString (timestamp, date);
uint8_t blindedPriv[64], blindedPub[128]; // 64 and 128 max
size_t publicKeyLen = blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub);
- std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKeyType, blindedPriv));
+ std::unique_ptr blindedSigner (i2p::data::PrivateKeys::CreateSigner (blindedKey.GetBlindedSigType (), blindedPriv));
auto offset = 1;
- htobe16buf (m_Buffer + offset, blindedKeyType); offset += 2; // Blinded Public Key Sig Type
+ htobe16buf (m_Buffer + offset, blindedKey.GetBlindedSigType ()); offset += 2; // Blinded Public Key Sig Type
memcpy (m_Buffer + offset, blindedPub, publicKeyLen); offset += publicKeyLen; // Blinded Public Key
htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
auto nextMidnight = (timestamp/86400LL + 1)*86400LL; // 86400 = 24*3600 seconds
@@ -847,12 +870,26 @@ namespace data
i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L1K", keys1);
offset += 32; // outerSalt
uint8_t * outerPlainText = m_Buffer + offset;
- m_Buffer[offset] = 0; offset++; // flag
+ m_Buffer[offset] = layer1Flags; offset++; // layer 1 flags
+ // auth data
+ uint8_t innerInput[68]; // authCookie || subcredential || publishedTimestamp
+ if (layer1Flags)
+ {
+ RAND_bytes (innerInput, 32); // authCookie
+ CreateClientAuthData (subcredential, authType, authKeys, innerInput, m_Buffer + offset);
+ offset += 32 + 2 + authKeys->size ()*40; // auth clients
+ }
// Layer 2
// keys = HKDF(outerSalt, outerInput, "ELS2_L2K", 44)
uint8_t keys2[64]; // 44 bytes actual data
RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32)
- i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2);
+ if (layer1Flags)
+ {
+ memcpy (innerInput + 32, subcredential, 36); // + subcredential || publishedTimestamp
+ i2p::crypto::HKDF (m_Buffer + offset, innerInput, 68, "ELS2_L2K", keys2);
+ }
+ else
+ i2p::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2); // no authCookie
offset += 32; // innerSalt
m_Buffer[offset] = ls->GetStoreType ();
memcpy (m_Buffer + offset + 1, ls->GetBuffer (), ls->GetBufferLen ());
@@ -879,6 +916,44 @@ namespace data
else
LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer");
}
-
+
+ void LocalEncryptedLeaseSet2::CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const
+ {
+ if (authType == ENCRYPTED_LEASESET_AUTH_TYPE_DH)
+ {
+ i2p::crypto::X25519Keys ek;
+ ek.GenerateKeys (); // esk and epk
+ memcpy (authData, ek.GetPublicKey (), 32); authData += 32; // epk
+ htobe16buf (authData, authKeys->size ()); authData += 2; // num clients
+ uint8_t authInput[100]; // sharedSecret || cpk_i || subcredential || publishedTimestamp
+ memcpy (authInput + 64, subcredential, 36);
+ for (auto& it: *authKeys)
+ {
+ ek.Agree (it, authInput); // sharedSecret = DH(esk, cpk_i)
+ memcpy (authInput + 32, it, 32);
+ uint8_t okm[64]; // 52 actual data
+ i2p::crypto::HKDF (ek.GetPublicKey (), authInput, 100, "ELS2_XCA", okm);
+ memcpy (authData, okm + 44, 8); authData += 8; // clientID_i
+ i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i
+ }
+ }
+ else // assume PSK
+ {
+ uint8_t authSalt[32];
+ RAND_bytes (authSalt, 32);
+ memcpy (authData, authSalt, 32); authData += 32; // authSalt
+ htobe16buf (authData, authKeys->size ()); authData += 2; // num clients
+ uint8_t authInput[68]; // authInput = psk_i || subcredential || publishedTimestamp
+ memcpy (authInput + 32, subcredential, 36);
+ for (auto& it: *authKeys)
+ {
+ memcpy (authInput, it, 32);
+ uint8_t okm[64]; // 52 actual data
+ i2p::crypto::HKDF (authSalt, authInput, 68, "ELS2PSKA", okm);
+ memcpy (authData, okm + 44, 8); authData += 8; // clientID_i
+ i2p::crypto::ChaCha20 (authCookie, 32, okm, okm + 32, authData); authData += 32; // clientCookie_i
+ }
+ }
+ }
}
}
diff --git a/libi2pd/LeaseSet.h b/libi2pd/LeaseSet.h
index a2ed6fba..70aa7110 100644
--- a/libi2pd/LeaseSet.h
+++ b/libi2pd/LeaseSet.h
@@ -79,9 +79,9 @@ namespace data
bool operator== (const LeaseSet& other) const
{ return m_BufferLen == other.m_BufferLen && !memcmp (m_Buffer, other.m_Buffer, m_BufferLen); };
virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
- virtual uint8_t GetOrigStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
virtual uint32_t GetPublishedTimestamp () const { return 0; }; // should be set for LeaseSet2 only
virtual std::shared_ptr GetTransientVerifier () const { return nullptr; };
+ virtual bool IsPublishedEncrypted () const { return false; };
// implements RoutingDestination
std::shared_ptr GetIdentity () const { return m_Identity; };
@@ -129,7 +129,9 @@ namespace data
const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7;
const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001;
-
+ const uint16_t LEASESET2_FLAG_UNPUBLISHED_LEASESET = 0x0002;
+ const uint16_t LEASESET2_FLAG_PUBLISHED_ENCRYPTED = 0x0004;
+
class LeaseSet2: public LeaseSet
{
public:
@@ -137,8 +139,9 @@ namespace data
LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true);
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr key, const uint8_t * secret = nullptr); // store type 5, called from local netdb only
uint8_t GetStoreType () const { return m_StoreType; };
- uint8_t GetOrigStoreType () const { return m_OrigStoreType; };
uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
+ bool IsPublic () const { return m_IsPublic; };
+ bool IsPublishedEncrypted () const { return m_IsPublishedEncrypted; };
std::shared_ptr GetTransientVerifier () const { return m_TransientVerifier; };
void Update (const uint8_t * buf, size_t len, bool verifySignature);
@@ -160,8 +163,9 @@ namespace data
private:
- uint8_t m_StoreType, m_OrigStoreType;
+ uint8_t m_StoreType;
uint32_t m_PublishedTimestamp = 0;
+ bool m_IsPublic = true, m_IsPublishedEncrypted = false;
std::shared_ptr m_TransientVerifier;
std::shared_ptr m_Encryptor; // for standardLS2
};
@@ -227,7 +231,8 @@ namespace data
LocalLeaseSet2 (uint8_t storeType, const i2p::data::PrivateKeys& keys,
uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey,
- std::vector > tunnels);
+ std::vector > tunnels,
+ bool isPublic, bool isPublishedEncrypted = false);
LocalLeaseSet2 (uint8_t storeType, std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP
virtual ~LocalLeaseSet2 () { delete[] m_Buffer; };
@@ -247,17 +252,28 @@ namespace data
size_t m_BufferLen;
};
+
+ const int ENCRYPTED_LEASESET_AUTH_TYPE_NONE = 0;
+ const int ENCRYPTED_LEASESET_AUTH_TYPE_DH = 1;
+ const int ENCRYPTED_LEASESET_AUTH_TYPE_PSK = 2;
+
+ typedef i2p::data::Tag<32> AuthPublicKey;
+
class LocalEncryptedLeaseSet2: public LocalLeaseSet2
{
public:
- LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, i2p::data::SigningKeyType blindedKeyType = i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519);
+ LocalEncryptedLeaseSet2 (std::shared_ptr ls, const i2p::data::PrivateKeys& keys, int authType = ENCRYPTED_LEASESET_AUTH_TYPE_NONE, std::shared_ptr > authKeys = nullptr);
LocalEncryptedLeaseSet2 (std::shared_ptr identity, const uint8_t * buf, size_t len); // from I2CP
const IdentHash& GetStoreHash () const { return m_StoreHash; };
std::shared_ptr GetInnerLeaseSet () const { return m_InnerLeaseSet; };
+ private:
+
+ void CreateClientAuthData (const uint8_t * subcredential, int authType, std::shared_ptr > authKeys, const uint8_t * authCookie, uint8_t * authData) const;
+
private:
IdentHash m_StoreHash;
diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp
index 368f1b3e..6d235abe 100644
--- a/libi2pd/NTCP2.cpp
+++ b/libi2pd/NTCP2.cpp
@@ -161,6 +161,7 @@ namespace transport
// fill options
uint8_t options[32]; // actual options size is 16 bytes
memset (options, 0, 16);
+ options[0] = i2p::context.GetNetID (); // network ID
options[1] = 2; // ver
htobe16buf (options + 2, paddingLength); // padLen
// m3p2Len
@@ -248,6 +249,11 @@ namespace transport
if (i2p::crypto::AEADChaCha20Poly1305 (m_SessionRequestBuffer + 32, 16, m_H, 32, m_K, nonce, options, 16, false)) // decrypt
{
// options
+ if (options[0] && options[0] != i2p::context.GetNetID ())
+ {
+ LogPrint (eLogWarning, "NTCP2: SessionRequest networkID ", (int)options[0], " mismatch. Expected ", i2p::context.GetNetID ());
+ return false;
+ }
if (options[1] == 2) // ver is always 2
{
paddingLen = bufbe16toh (options + 2);
diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp
index b7479744..4461c094 100644
--- a/libi2pd/NetDb.cpp
+++ b/libi2pd/NetDb.cpp
@@ -307,10 +307,18 @@ namespace data
if (it == m_LeaseSets.end () || it->second->GetStoreType () != storeType ||
leaseSet->GetPublishedTimestamp () > it->second->GetPublishedTimestamp ())
{
- // TODO: implement actual update
- LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
- m_LeaseSets[ident] = leaseSet;
- return true;
+ if (leaseSet->IsPublic ())
+ {
+ // TODO: implement actual update
+ LogPrint (eLogInfo, "NetDb: LeaseSet2 updated: ", ident.ToBase32());
+ m_LeaseSets[ident] = leaseSet;
+ return true;
+ }
+ else
+ {
+ LogPrint (eLogWarning, "NetDb: Unpublished LeaseSet2 received: ", ident.ToBase32());
+ m_LeaseSets.erase (ident);
+ }
}
}
else
diff --git a/libi2pd/SSUSession.cpp b/libi2pd/SSUSession.cpp
index a7497fd1..88dbcf04 100644
--- a/libi2pd/SSUSession.cpp
+++ b/libi2pd/SSUSession.cpp
@@ -1,4 +1,5 @@
#include
+#include "version.h"
#include "Crypto.h"
#include "Log.h"
#include "Timestamp.h"
@@ -729,7 +730,8 @@ namespace transport
encryption.Encrypt (encrypted, encryptedLen, encrypted);
// assume actual buffer size is 18 (16 + 2) bytes more
memcpy (buf + len, iv, 16);
- htobe16buf (buf + len + 16, encryptedLen);
+ uint16_t netid = i2p::context.GetNetID ();
+ htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8));
i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac);
}
@@ -750,7 +752,8 @@ namespace transport
m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted);
// assume actual buffer size is 18 (16 + 2) bytes more
memcpy (buf + len, header->iv, 16);
- htobe16buf (buf + len + 16, encryptedLen);
+ uint16_t netid = i2p::context.GetNetID ();
+ htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8));
i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac);
}
@@ -799,7 +802,8 @@ namespace transport
uint16_t encryptedLen = len - (encrypted - buf);
// assume actual buffer size is 18 (16 + 2) bytes more
memcpy (buf + len, header->iv, 16);
- htobe16buf (buf + len + 16, encryptedLen);
+ uint16_t netid = i2p::context.GetNetID ();
+ htobe16buf (buf + len + 16, (netid == I2PD_NET_ID) ? encryptedLen : encryptedLen ^ ((netid - 2) << 8));
uint8_t digest[16];
i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest);
return !memcmp (header->mac, digest, 16);
diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp
index 7b1f187b..4f943912 100644
--- a/libi2pd/Streaming.cpp
+++ b/libi2pd/Streaming.cpp
@@ -918,7 +918,7 @@ namespace stream
{
expired = false;
// time to request
- if (m_RemoteLeaseSet->GetOrigStoreType () == i2p::data::NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
+ if (m_RemoteLeaseSet->IsPublishedEncrypted ())
m_LocalDestination.GetOwner ()->RequestDestinationWithEncryptedLeaseSet (
std::make_shared(m_RemoteIdentity));
else
@@ -964,7 +964,6 @@ namespace stream
StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip):
m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip),
- m_LastIncomingReceiveStreamID (0),
m_PendingIncomingTimer (m_Owner->GetService ())
{
}
@@ -991,6 +990,7 @@ namespace stream
{
std::unique_lock l(m_StreamsMutex);
m_Streams.clear ();
+ m_IncomingStreams.clear ();
}
}
@@ -1013,18 +1013,17 @@ namespace stream
if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream
{
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
- if (receiveStreamID == m_LastIncomingReceiveStreamID)
+ auto it1 = m_IncomingStreams.find (receiveStreamID);
+ if (it1 != m_IncomingStreams.end ())
{
// already pending
LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists");
DeletePacket (packet); // drop it, because previous should be connected
return;
}
- auto incomingStream = CreateNewIncomingStream ();
+ auto incomingStream = CreateNewIncomingStream (receiveStreamID);
incomingStream->HandleNextPacket (packet); // SYN
auto ident = incomingStream->GetRemoteIdentity();
-
- m_LastIncomingReceiveStreamID = receiveStreamID;
// handle saved packets if any
{
@@ -1062,13 +1061,13 @@ namespace stream
else // follow on packet without SYN
{
uint32_t receiveStreamID = packet->GetReceiveStreamID ();
- for (auto& it: m_Streams)
- if (it.second->GetSendStreamID () == receiveStreamID)
- {
- // found
- it.second->HandleNextPacket (packet);
- return;
- }
+ auto it1 = m_IncomingStreams.find (receiveStreamID);
+ if (it1 != m_IncomingStreams.end ())
+ {
+ // found
+ it1->second->HandleNextPacket (packet);
+ return;
+ }
// save follow on packet
auto it = m_SavedPackets.find (receiveStreamID);
if (it != m_SavedPackets.end ())
@@ -1105,11 +1104,12 @@ namespace stream
return s;
}
- std::shared_ptr StreamingDestination::CreateNewIncomingStream ()
+ std::shared_ptr StreamingDestination::CreateNewIncomingStream (uint32_t receiveStreamID)
{
auto s = std::make_shared (m_Owner->GetService (), *this);
std::unique_lock l(m_StreamsMutex);
m_Streams[s->GetRecvStreamID ()] = s;
+ m_IncomingStreams[receiveStreamID] = s;
return s;
}
@@ -1118,9 +1118,8 @@ namespace stream
if (stream)
{
std::unique_lock l(m_StreamsMutex);
- auto it = m_Streams.find (stream->GetRecvStreamID ());
- if (it != m_Streams.end ())
- m_Streams.erase (it);
+ m_Streams.erase (stream->GetRecvStreamID ());
+ m_IncomingStreams.erase (stream->GetSendStreamID ());
}
}
diff --git a/libi2pd/Streaming.h b/libi2pd/Streaming.h
index ba52464f..49962507 100644
--- a/libi2pd/Streaming.h
+++ b/libi2pd/Streaming.h
@@ -269,8 +269,10 @@ namespace stream
void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev);
+ private:
+
void HandleNextPacket (Packet * packet);
- std::shared_ptr CreateNewIncomingStream ();
+ std::shared_ptr CreateNewIncomingStream (uint32_t receiveStreamID);
void HandlePendingIncomingTimer (const boost::system::error_code& ecode);
private:
@@ -280,8 +282,8 @@ namespace stream
bool m_Gzip; // gzip compression of data messages
std::mutex m_StreamsMutex;
std::map > m_Streams; // sendStreamID->stream
+ std::map > m_IncomingStreams; // receiveStreamID->stream
Acceptor m_Acceptor;
- uint32_t m_LastIncomingReceiveStreamID;
std::list > m_PendingIncomingStreams;
boost::asio::deadline_timer m_PendingIncomingTimer;
std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN
diff --git a/libi2pd/version.h b/libi2pd/version.h
index cd96c677..1c08a362 100644
--- a/libi2pd/version.h
+++ b/libi2pd/version.h
@@ -7,7 +7,7 @@
#define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c)
#define I2PD_VERSION_MAJOR 2
-#define I2PD_VERSION_MINOR 27
+#define I2PD_VERSION_MINOR 28
#define I2PD_VERSION_MICRO 0
#define I2PD_VERSION_PATCH 0
#define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO)
@@ -21,7 +21,7 @@
#define I2P_VERSION_MAJOR 0
#define I2P_VERSION_MINOR 9
-#define I2P_VERSION_MICRO 41
+#define I2P_VERSION_MICRO 42
#define I2P_VERSION_PATCH 0
#define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO)
diff --git a/libi2pd_client/AddressBook.cpp b/libi2pd_client/AddressBook.cpp
index 75d95da1..14ed89a7 100644
--- a/libi2pd_client/AddressBook.cpp
+++ b/libi2pd_client/AddressBook.cpp
@@ -422,9 +422,9 @@ namespace client
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
- size_t pos = s.find('#');
+ size_t pos = addr.find('#');
if (pos != std::string::npos)
- addr = addr.substr(pos); // remove comments
+ addr = addr.substr(0, pos); // remove comments
auto ident = std::make_shared ();
if (!ident->FromBase64(addr)) {
diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp
index c286007c..d14491b2 100644
--- a/libi2pd_client/ClientContext.cpp
+++ b/libi2pd_client/ClientContext.cpp
@@ -381,6 +381,16 @@ namespace client
return section.second.get (boost::property_tree::ptree::path_type (name, '/'), value);
}
+ template
+ void ClientContext::ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const
+ {
+ for (auto it: section.second)
+ {
+ if (it.first.length () >= group.length () && !it.first.compare (0, group.length (), group))
+ options[it.first] = it.second.get_value ("");
+ }
+ }
+
template
void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const
{
@@ -397,6 +407,15 @@ namespace client
if (encType.length () > 0) options[I2CP_PARAM_LEASESET_ENCRYPTION_TYPE] = encType;
std::string privKey = GetI2CPStringOption(section, I2CP_PARAM_LEASESET_PRIV_KEY, "");
if (privKey.length () > 0) options[I2CP_PARAM_LEASESET_PRIV_KEY] = privKey;
+ auto authType = GetI2CPOption(section, I2CP_PARAM_LEASESET_AUTH_TYPE, 0);
+ if (authType != "0") // auth is set
+ {
+ options[I2CP_PARAM_LEASESET_AUTH_TYPE] = authType;
+ if (authType == "1") // DH
+ ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_DH, options);
+ else if (authType == "2") // PSK
+ ReadI2CPOptionsGroup (section, I2CP_PARAM_LEASESET_CLIENT_PSK, options);
+ }
}
void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const
@@ -610,8 +629,8 @@ namespace client
// I2CP
std::map options;
- ReadI2CPOptions (section, options);
-
+ ReadI2CPOptions (section, options);
+
std::shared_ptr localDestination = nullptr;
i2p::data::PrivateKeys k;
if(!LoadPrivateKeys (k, keys, sigType, cryptoType))
diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h
index 71e052c3..94aa8594 100644
--- a/libi2pd_client/ClientContext.h
+++ b/libi2pd_client/ClientContext.h
@@ -95,6 +95,8 @@ namespace client
template
std::string GetI2CPStringOption (const Section& section, const std::string& name, const std::string& value) const; // GetI2CPOption with string default value
template
+ void ReadI2CPOptionsGroup (const Section& section, const std::string& group, std::map& options) const;
+ template
void ReadI2CPOptions (const Section& section, std::map& options) const; // for tunnels
void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; // for HTTP and SOCKS proxy
diff --git a/libi2pd_client/SAM.cpp b/libi2pd_client/SAM.cpp
index acb9ea80..766a1940 100644
--- a/libi2pd_client/SAM.cpp
+++ b/libi2pd_client/SAM.cpp
@@ -239,6 +239,7 @@ namespace client
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
if (eol)
{
+ if (eol > m_Buffer && eol[-1] == '\r') eol--;
*eol = 0;
char * separator = strchr (m_Buffer, ' ');
if (separator)
@@ -259,7 +260,7 @@ namespace client
ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP))
ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
- else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND))
+ else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND) || !strcmp (m_Buffer, SAM_RAW_SEND))
{
size_t len = bytes_transferred - (separator - m_Buffer) - 1;
size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1);
@@ -337,8 +338,20 @@ namespace client
return;
}
+ SAMSessionType type = eSAMSessionTypeUnknown;
+ if (style == SAM_VALUE_STREAM) type = eSAMSessionTypeStream;
+ else if (style == SAM_VALUE_DATAGRAM) type = eSAMSessionTypeDatagram;
+ else if (style == SAM_VALUE_RAW) type = eSAMSessionTypeRaw;
+ if (type == eSAMSessionTypeUnknown)
+ {
+ // unknown style
+ SendI2PError("Unknown STYLE");
+ return;
+ }
+
std::shared_ptr forward = nullptr;
- if (style == SAM_VALUE_DATAGRAM && params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end())
+ if ((type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw) &&
+ params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end())
{
// udp forward selected
boost::system::error_code e;
@@ -379,16 +392,20 @@ namespace client
}
// create destination
- auto session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms);
+ auto session = m_Owner.CreateSession (id, type, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms);
if (session)
{
m_SocketType = eSAMSocketTypeSession;
- if (style == SAM_VALUE_DATAGRAM)
+ if (type == eSAMSessionTypeDatagram || type == eSAMSessionTypeRaw)
{
session->UDPEndpoint = forward;
auto dest = session->localDestination->CreateDatagramDestination ();
- dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
- std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
+ if (type == eSAMSessionTypeDatagram)
+ dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
+ else // raw
+ dest->SetRawReceiver (std::bind (&SAMSocket::HandleI2PRawDatagramReceive, shared_from_this (),
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
}
if (session->localDestination->IsReady ())
@@ -550,7 +567,10 @@ namespace client
{
i2p::data::IdentityEx dest;
dest.FromBase64 (params[SAM_PARAM_DESTINATION]);
- d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
+ if (session->Type == eSAMSessionTypeDatagram)
+ d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
+ else // raw
+ d->SendRawDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM: missing datagram destination");
@@ -926,16 +946,44 @@ namespace client
}
}
+ void SAMSocket::HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
+ {
+ LogPrint (eLogDebug, "SAM: raw datagram received ", len);
+ auto session = m_Owner.FindSession(m_ID);
+ if(session)
+ {
+ auto ep = session->UDPEndpoint;
+ if (ep)
+ // udp forward enabled
+ m_Owner.SendTo(buf, len, ep);
+ else
+ {
+#ifdef _MSC_VER
+ size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len);
+#else
+ size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_RAW_RECEIVED, (long unsigned int)len);
+#endif
+ if (len < SAM_SOCKET_BUFFER_SIZE - l)
+ {
+ memcpy (m_StreamBuffer + l, buf, len);
+ WriteI2PData(len + l);
+ }
+ else
+ LogPrint (eLogWarning, "SAM: received raw datagram size ", len," exceeds buffer");
+ }
+ }
+ }
+
void SAMSocket::HandleStreamSend(const boost::system::error_code & ec)
{
m_Owner.GetService ().post (std::bind( !ec ? &SAMSocket::Receive : &SAMSocket::TerminateClose, shared_from_this()));
}
- SAMSession::SAMSession (SAMBridge & parent, const std::string & id, std::shared_ptr dest):
+ SAMSession::SAMSession (SAMBridge & parent, const std::string & id, SAMSessionType type, std::shared_ptr dest):
m_Bridge(parent),
localDestination (dest),
UDPEndpoint(nullptr),
- Name(id)
+ Name(id), Type (type)
{
}
@@ -964,7 +1012,8 @@ namespace client
{"ECDSA_SHA256_P521", i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA512_P521},
{"EdDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519},
{"GOST_GOSTR3411256_GOSTR3410CRYPTOPROA", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256},
- {"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512}
+ {"GOST_GOSTR3411512_GOSTR3410TC26A512", i2p::data::SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512},
+ {"RedDSA_SHA512_Ed25519", i2p::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519},
}
{
}
@@ -1061,8 +1110,8 @@ namespace client
Accept ();
}
- std::shared_ptr SAMBridge::CreateSession (const std::string& id, const std::string& destination,
- const std::map * params)
+ std::shared_ptr SAMBridge::CreateSession (const std::string& id, SAMSessionType type,
+ const std::string& destination, const std::map * params)
{
std::shared_ptr localDestination = nullptr;
if (destination != "")
@@ -1102,7 +1151,7 @@ namespace client
if (localDestination)
{
localDestination->Acquire ();
- auto session = std::make_shared(*this, id, localDestination);
+ auto session = std::make_shared(*this, id, type, localDestination);
std::unique_lock l(m_SessionsMutex);
auto ret = m_Sessions.insert (std::make_pair(id, session));
if (!ret.second)
@@ -1193,8 +1242,12 @@ namespace client
{
i2p::data::IdentityEx dest;
dest.FromBase64 (destination);
- session->localDestination->GetDatagramDestination ()->
- SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
+ if (session->Type == eSAMSessionTypeDatagram)
+ session->localDestination->GetDatagramDestination ()->
+ SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
+ else // raw
+ session->localDestination->GetDatagramDestination ()->
+ SendRawDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM: Session ", sessionID, " not found");
diff --git a/libi2pd_client/SAM.h b/libi2pd_client/SAM.h
index 7cd3fd4c..aebdccb6 100644
--- a/libi2pd_client/SAM.h
+++ b/libi2pd_client/SAM.h
@@ -40,12 +40,14 @@ namespace client
const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n";
const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND";
+ const char SAM_RAW_SEND[] = "RAW SEND";
const char SAM_DEST_GENERATE[] = "DEST GENERATE";
const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n";
const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n";
const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP";
const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n";
const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n";
+ const char SAM_RAW_RECEIVED[] = "RAW RECEIVED SIZE=%lu\n";
const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n";
const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=KEY_NOT_FOUND NAME=%s\n";
const char SAM_PARAM_MIN[] = "MIN";
@@ -111,6 +113,7 @@ namespace client
void HandleI2PAccept (std::shared_ptr stream);
void HandleWriteI2PData (const boost::system::error_code& ecode, size_t sz);
void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
+ void HandleI2PRawDatagramReceive (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void ProcessSessionCreate (char * buf, size_t len);
void ProcessStreamConnect (char * buf, size_t len, size_t rem);
@@ -149,14 +152,23 @@ namespace client
std::shared_ptr m_Stream;
};
+ enum SAMSessionType
+ {
+ eSAMSessionTypeUnknown,
+ eSAMSessionTypeStream,
+ eSAMSessionTypeDatagram,
+ eSAMSessionTypeRaw
+ };
+
struct SAMSession
{
SAMBridge & m_Bridge;
std::shared_ptr localDestination;
std::shared_ptr UDPEndpoint;
std::string Name;
+ SAMSessionType Type;
- SAMSession (SAMBridge & parent, const std::string & name, std::shared_ptr dest);
+ SAMSession (SAMBridge & parent, const std::string & name, SAMSessionType type, std::shared_ptr dest);
~SAMSession ();
void CloseStreams ();
@@ -173,7 +185,7 @@ namespace client
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
- std::shared_ptr CreateSession (const std::string& id, const std::string& destination, // empty string means transient
+ std::shared_ptr CreateSession (const std::string& id, SAMSessionType type, const std::string& destination, // empty string means transient
const std::map * params);
void CloseSession (const std::string& id);
std::shared_ptr FindSession (const std::string& id) const;
diff --git a/qt/i2pd_qt/.gitignore b/qt/i2pd_qt/.gitignore
index 3abca1bd..f1d57c58 100644
--- a/qt/i2pd_qt/.gitignore
+++ b/qt/i2pd_qt/.gitignore
@@ -6,4 +6,6 @@ i2pd_qt
Makefile*
*.stash
object_script.*
-i2pd_qt_plugin_import.cpp
\ No newline at end of file
+i2pd_qt_plugin_import.cpp
+i2pd_qt.pro.autosave*
+
diff --git a/qt/i2pd_qt/DaemonQT.cpp b/qt/i2pd_qt/DaemonQT.cpp
index f5e6d62b..1e45c583 100644
--- a/qt/i2pd_qt/DaemonQT.cpp
+++ b/qt/i2pd_qt/DaemonQT.cpp
@@ -11,6 +11,8 @@
#include
#include
+//#define DEBUG_WITH_DEFAULT_LOGGING (1)
+
namespace i2p
{
namespace qt
@@ -151,10 +153,16 @@ namespace qt
int result;
{
- std::shared_ptr logstreamptr=std::make_shared();
+ std::shared_ptr logstreamptr=
+#ifdef DEBUG_WITH_DEFAULT_LOGGING
+ nullptr
+#else
+ std::make_shared()
+#endif
+ ;
//TODO move daemon init deinit to a bg thread
DaemonQTImpl daemon;
- (*logstreamptr) << "Initialising the daemon..." << std::endl;
+ if(logstreamptr) (*logstreamptr) << "Initialising the daemon..." << std::endl;
bool daemonInitSuccess = daemon.init(argc, argv, logstreamptr);
if(!daemonInitSuccess)
{
diff --git a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml
index b16d0d5c..65683584 100644
--- a/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml
+++ b/qt/i2pd_qt/data/website.i2pd.i2pd.appdata.xml
@@ -35,6 +35,7 @@
+
diff --git a/qt/i2pd_qt/i2pd_qt.pro b/qt/i2pd_qt/i2pd_qt.pro
index 9a978731..51aa4246 100644
--- a/qt/i2pd_qt/i2pd_qt.pro
+++ b/qt/i2pd_qt/i2pd_qt.pro
@@ -5,7 +5,12 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = i2pd_qt
TEMPLATE = app
QMAKE_CXXFLAGS *= -std=c++11 -Wno-unused-parameter -Wno-maybe-uninitialized
-DEFINES += USE_UPNP
+
+# For now, disable UPnP which currently crashes on Stop() -- https://github.com/PurpleI2P/i2pd/issues/1387
+#DEFINES += USE_UPNP
+DEFINES -= USE_UPNP
+
+debug: DEFINES += DEBUG_WITH_DEFAULT_LOGGING
SOURCES += DaemonQT.cpp mainwindow.cpp \
../../libi2pd/api.cpp \
diff --git a/qt/i2pd_qt/logviewermanager.cpp b/qt/i2pd_qt/logviewermanager.cpp
index 30fc904a..823956b2 100644
--- a/qt/i2pd_qt/logviewermanager.cpp
+++ b/qt/i2pd_qt/logviewermanager.cpp
@@ -18,7 +18,7 @@ namespace logviewer {
QString Worker::pollAndShootATimerForInfiniteRetries() {
std::shared_ptr logStream=logViewerManager.getLogStream();
- assert(logStream!=nullptr);
+ if(!logStream)return "";
std::streamsize MAX_SZ=64*1024;
char*buf=(char*)malloc(MAX_SZ*sizeof(char));
if(buf==nullptr)return "";