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 "";