Compare commits

..
Author SHA1 Message Date
Vitaliy Filippov c6af10a444 WIP Support TLS certificate generation in make-etcd 2026-05-19 17:20:30 +03:00
Vitaliy Filippov bc24936969 Fix use_auth usage 2026-05-19 17:19:47 +03:00
Vitaliy Filippov b6c854c860 Add support for full AES-GCM including double encryption of AES-XTS :D 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 0ce6a61fcb Also allow clients to load /inode/stats/ for their inodes 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 4498d0665a Load all /config/ 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 55cce76d36 Fix modify owner/groups 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 70c0cd2d5f Skip non-existing pools 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 455dc9cb1c Fix ipv6 support in antietcd_adapter 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 9243aed615 Use osd & mon certs instead of usernames 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 3dd0e4daca Remove mon and osd user types 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 3779e2670d Also check for "Received garbage" during test 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 0d92e00e41 Add a CI test with RDMA 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 0d1eeb4310 Add request size validation to prevent OOMDoS 2026-05-19 17:19:47 +03:00
Vitaliy Filippov b21c92cb7a Implement OSD-side authorization for operations 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 9db0980fc1 Remove tls_ from option names 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 4a8ee088c1 Remove TLS support (superseded by direct GCM) 2026-05-19 17:19:47 +03:00
Vitaliy Filippov f2585bcd02 Implement TLS 1.3-like handshake manually 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 72d1447eec Extract openssl-related code, wire ssl implementation back 2026-05-19 17:19:47 +03:00
Vitaliy Filippov c67db91305 Coalesce entries in send_out_buf 2026-05-19 17:19:47 +03:00
Vitaliy Filippov 75dfafa0cf Support isa-l_crypto for AES-XTS too 2026-05-19 17:19:47 +03:00
Vitaliy Filippov b1d9098904 Fix xts+rdma encrypt errors 2026-05-19 17:19:35 +03:00
Vitaliy Filippov f676d262ec Fix "use-after-realloc" warning 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 954505b5fd Support isa-l_crypto for AES-GCM 2026-05-19 17:19:35 +03:00
Vitaliy Filippov c137860f18 Remove WITH_OPENSSL from all files except http_client, always require OpenSSL 2026-05-19 17:19:35 +03:00
Vitaliy Filippov f45fb987d1 Use pools for GCM contexts 2026-05-19 17:19:35 +03:00
Vitaliy Filippov d744bd43ae Try to run tests with AES-GCM 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 8da99ee24a Implement direct AES-256-GCM with a static key for benchmark 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 3c3a58aa1e Make sure to send all TLS data before continuing 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 40d4162249 Parse standard TLS record headers 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 026aaf59e7 Omit msgr_tls_record_hdr_t for non-tls data 2026-05-19 17:19:35 +03:00
Vitaliy Filippov cb40ab3dfd Allow 2 and 4 byte per block chain_info (allow more than 255 snapshots with encryption) 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 459a412f91 Implement OSD TLS support 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 2b52769d64 Allow to skip checksums for headers 2026-05-19 17:19:35 +03:00
Vitaliy Filippov ff83e12b79 Implement protocol-level checksums (xxhash3) 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 4340b2b7bc Include xxhash3 x86dispatch 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 6be0be02fa Do not use scrap_buffer in the client (it would block protocol checksum support) 2026-05-19 17:19:35 +03:00
Vitaliy Filippov a31a1c0ab1 Support TLS CN authentication and per-image permissions in vitastor-cli serve 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 1c18daa644 Add VitastorAuthFilter 2026-05-19 17:19:35 +03:00
Vitaliy Filippov b367c81f44 Implement vitastor-cli ls-user, modify-user, remove-user commands 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 197ef6c171 Add image owner/owner_group/reader_group support (for antietcd VitastorAuthFilter) 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 4a907f09df Add security parameter documentation 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 360ccbaa4d Support inline (string PEM) certificates and pkeys 2026-05-19 17:19:35 +03:00
Vitaliy Filippov d960816a9b Show encryption keys (only IDs) in the listing 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 3dabffb1be Support storing image encryption keys in Vault 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 5ba63a2fa5 Prefer local etcd addresses and correctly cycle over them even when they need resolving
Seems slightly overcomplicated...
2026-05-19 17:19:35 +03:00
Vitaliy Filippov 26be916e3f Support DNS resolving via libc-ares 2026-05-19 17:19:35 +03:00
Vitaliy Filippov eb1dbd7459 Batch handle_immediate_ops more 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 13842ecd90 Add vitastor-cli create & modify --enc-key parameter 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 27e2c08e38 Support reading from snapshots encrypted with different keys 2026-05-19 17:19:35 +03:00
Vitaliy Filippov f9975311ea Support decryption with multiple keys 2026-05-19 17:19:35 +03:00
Vitaliy Filippov 652ca3f1c3 Allow to return chain_info in response to reads 2026-05-19 17:19:35 +03:00
Vitaliy Filippov c81cbcf69e Add basic AES-XTS client-side encryption support 2026-05-19 17:19:34 +03:00
Vitaliy Filippov 9e28c4c6e3 Rework msgr send/receive to allow encryption support 2026-05-19 17:19:16 +03:00
Vitaliy Filippov e14be3fdad Move fromhexstr() to str_util 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 2e5d9cb238 Add openapi description 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 376226552a Slightly fix API return and input types 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 6bdd260b50 Implement vitastor-cli serve command to serve simple HTTP API 2026-05-19 17:19:16 +03:00
Vitaliy Filippov b72cfbad66 Implement HTTP server support O_o 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 792efa673e Rename http_response_t to http_message_t 2026-05-19 17:19:16 +03:00
Vitaliy Filippov f3c5c3b776 Extract common HTTP context 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 5e26376f1f Support xxhash 32-bit checksums (data_csum_type=xxh3_32) 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 55047c10fb Detect block checksums using csum_block_size, not data_csum_type 2026-05-19 17:19:16 +03:00
Vitaliy Filippov e13edcdc2f Add client certificate support 2026-05-19 17:19:16 +03:00
Vitaliy Filippov aaf54f264d Do not re-initialize TLS context every connection 2026-05-19 17:19:16 +03:00
Vitaliy Filippov 7e879bea9d Add https support to antietcd 2026-05-19 17:19:16 +03:00
Vitaliy Filippov af34be70ab Implement etcd SSL support via OpenSSL
Maybe I should remove all of this and use libwebsockets :)
2026-05-19 17:19:16 +03:00
Vitaliy Filippov 00517e2bac Fix header checksum after new store write-meta 2026-05-19 12:30:21 +03:00
Vitaliy Filippov e0a2615cbc Fix test build 2026-05-19 02:27:03 +03:00
Vitaliy Filippov 63fe3c323a Release 3.0.12
Important fixes (except the new store):

- Fixed a possible use-after-free in the OSD during error handling of initial
  commit/rollback of objects in EC pools.
- Fixed a possible free of an invalid pointer in the OSD during read errors
  from snapshot/clone chains in EC pools.
- Fixed possibly incorrect handling of commit/rollback operations in EC pools
  during pool PG count changes.
- Fixed the inverted fsync enable parameter in the ublk driver (fsync was not
  enabled on pools without immediate_commit).
- Added the raw-ls command for debugging purposes to find object versions in
  the cluster using listing operations.

New store fixes:

- Improved startup speed by using LSN-based sorting only for objects with a
  large number of intermediate versions.
- Added skip_double_claim option as a temporary workaround to fix the rare OSD
  startup error with the "double claimed block" message, observed by several
  users. This option does not affect data integrity.
- Fixed incorrect rechecking of small writes during startup, which in theory
  could lead to duplicate small write object entries on the OSD.
- Fixed fsync operation for disks with a writeback cache (without capacitors):
  - Fixed incorrect semantics of consecutive fsyncs (next fsync was not blocked
    by the previous one).
  - Added fsync when copying small writes from the buffer to the data device
    (somehow forgotten during initial development).
  - Added fsync after the initial garbage collection during OSD startup.
  - Fixed incorrect cast of LSN from uint64 to uint32, breaking fsync when
    reaching LSN 2^32.
- Added missing verification of the metadata header checksum during startup.
- Fixed incorrect updating of object checksums in perfect_csum_update=true mode.
- Fixed a possible OSD crash with "assertion failed" when processing a malformed
  EC STABILIZE operation.
- Fixed the accounting of active compactor coroutines.
- Removed broken and untested new->old store conversion support.

Minor issues fixed:

- Incorrect accounting of OSD local operation statistics in replicated pools.
- Missing non-zero exitcodes on vitastor-disk resize command errors.
- Missing reset of the list of inconsistent objects during PG restarts.
- Theoretically possible hangs of various OSD operations when working with
  completely corrupted objects (without a single available copy), and possibly
  in some other very rare situations.
- Incorrect fsyncs when deleting objects from pools without immediate_commit
  (on disks with a writeback cache), which previously could leave garbage when
  deleting misplaced objects.
- Possible crash/memory corruption of the NFS server during a targeted attack
  on NFS-RDMA.
- Possibly incorrect handling of ENOSPC/EIO write errors in replicated pools,
  leading to inability to retry the write later.
- Possible crash instead of a clean error exit when starting an OSD with the
  old storage engine on a disk with corrupted journal data.
- Shallow copying of PG configuration in the monitor, however, not related to
  actual bugs.
- Incorrect checking of allocated blocks in the QEMU driver in an unused code
  branch (without the BDRV_WANT_ZERO flag).
- Possible memory leak on read errors of corrupted objects.
- Possible incorrect PG states when corrupted objects are detected.
- Possible failure to mark all "bad" copies of an object during scrubs without
  checksums and with a large number of replicas (> 4).
- Incorrect checksum calculation in the old storage engine when
  bitmap_granularity < 4096 (a practically unused configuration).
- Theoretically possible OSD crash in rare cases during a scrub and simultaneous
  object recovery.
- Theoretically possible OSD crash when handling PING operation errors.
- Slightly suboptimal logic for reusing the RDMA send buffer.
- Possible memory leak when canceling an already running scrub via no_scrub.
- Possible memory corruption when a client (e.g., QEMU code) passes invalid
  buffers and the writeback cache is enabled.
- Potentially incorrect search for corrupted parts of EC objects (inability
  to find a "good" combination) during a scrub with checksums disabled.
- Possible additional memory usage on the OSD side when handling failed reads
  from snapshots (not a leak however - the memory was freed upon client
  disconnection).
- Potential sudden write slowdown at certain pg epoch values due to incorrect
  epoch update logic in etcd.
2026-05-18 02:24:22 +03:00
Vitaliy Filippov a88465df05 Fix build under older gcc 2026-05-18 01:17:56 +03:00
Vitaliy Filippov ad24be717a Wrap delete rdma_conn into destroy_rdma_conn 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 648e3b12f0 Replace public/protected ifdef with a friend class 2026-05-18 01:17:56 +03:00
Vitaliy Filippov a675993c74 Remove unused osd_test.cpp and mock/ringloop.h, move epmgr out of osd_t 2026-05-18 01:17:56 +03:00
Vitaliy Filippov c9dfd0f67d Fix possible buffer overflow in NFS-RDMA 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 84919a10a9 Extract msgr_iothread to separate .{cpp,h} 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 51ae4d6e24 Remove extra assert in heap after reworking recheck 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 572b20fedc Fix mark_partial_write for replicas (TODO: try to write an OSD unit test for it) 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 4e2724b28f Remove duplicate subop submit/fail code 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 768b1675f8 Do not use ISA-L in test_crc32c 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 38fa722725 Remove the rest of tcmalloc from build 2026-05-18 01:17:56 +03:00
Vitaliy Filippov e56d83fb7f Remove heap->v1 conversion code (untested, unused and probably buggy) 2026-05-18 01:17:56 +03:00
Vitaliy Filippov ff95a85875 Add bound check to je_crc32 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 98203568a8 Handle ENOENT from add_commit 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 89df98ee08 Deep copy pg_history and prev_pgs before mutation 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 0007a831b6 Add return to handle_flush_op when stopping the PG 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 40517c335f Fix absolute bmp_end 2026-05-18 01:17:56 +03:00
Vitaliy Filippov c9f7308b6a Wrap this submit_ into n_subops > 0 too 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 85c7e3bde0 Fix inverted writeback in ublk 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 4fb55b3535 Rework recheck_small_writes to free multiple bad entries more correctly and not use garbage as "already freed" flag 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 912aca11a3 Improve test_recheck to check all possible combinations 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 7b454bd16c Do not try to free snapshot_bitmaps 2026-05-18 01:17:56 +03:00
Vitaliy Filippov a0c8be46a4 Speedup blockstore_heap loading by postponing only objects with a lot of versions 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 53b4329fac Add missing active_flushers-- 2026-05-18 01:17:56 +03:00
Vitaliy Filippov a7f41c4a12 Add assert to reconstruct_stripes_ec 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 5d78057ac3 Fix cur_op->buf memory leak on EIO/EDOM reads(?) 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 8efc5a353f Fix pg_state_bits overwriting PG_HAS_CORRUPTED 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 603b26b896 Add a FIXME about delete buffering 2026-05-18 01:17:56 +03:00
Vitaliy Filippov a3b0fe0deb Fix op stats not tracked for OP_WRITE_STABLE 2026-05-18 01:17:56 +03:00
Vitaliy Filippov f504e356d5 Fix schedule_next_recheck_at 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 4ed17b7070 Sync metadata after writing out recheck_modified_blocks 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 1fd2819724 Fix marking only first OSD from non-majority group as corrupted during scrub
Note: Could be tested with 5 replicas - 3 good and 2 corrupted if we had OSD unit tests
2026-05-18 01:17:56 +03:00
Vitaliy Filippov dcdabbc1ec Fix vitastor-disk resize error return codes missed 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 625d5b7b9e Fix punch_holes not updating entry crc (bug when perfect_csum_update==true) 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 9e507fd333 Fix typo in crc32_pad
Didn't affect anything though because it was only skipping < 4096 byte remainders
2026-05-18 01:17:56 +03:00
Vitaliy Filippov c2b5118127 Add data device fsyncs during compaction (forgotten during initial heap store dev) 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 4b926e2223 Add a test for missing data device fsyncs 2026-05-18 01:17:56 +03:00
Vitaliy Filippov a5d9a6996a Do not try to increment ref_count if recheck_state is null 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 0ee03e7172 Add reply == request opcode check (also just in case) 2026-05-18 01:17:56 +03:00
Vitaliy Filippov 88b7d9afcd Expect to receive bitmap only for successfull response 2026-05-18 01:17:56 +03:00
Vitaliy Filippov f271c8450c Block sync by all previous syncs 2026-05-11 16:33:49 +03:00
Vitaliy Filippov f78d7d4efc Do not capture client pointer in ping lambda 2026-05-11 16:30:01 +03:00
Vitaliy Filippov fdaf7c88ff Fix RDMA send buffer wraparound - could only lead to slower sending but not to corruption because the bug was canceled by send_out_pos >= send_done_pos check 2026-05-11 16:28:03 +03:00
Vitaliy Filippov 2fb6eb0c30 Free scrub_cur_list.buf when turning on no_scrub (potential memory leak) 2026-05-11 16:15:48 +03:00
Vitaliy Filippov 36d2b56208 Deep copy merged_history, also just in case 2026-05-11 16:14:03 +03:00
Vitaliy Filippov 14b22f2ba9 Retry even fatal errors for OP_SYNC (no real-world issue, just in case) 2026-05-11 16:08:14 +03:00
Vitaliy Filippov fe8b1fe0cc Add assert in cluster_client writeback to prevent overflow when incorrect operation data is passed 2026-05-11 16:06:19 +03:00
Vitaliy Filippov 1ec963e468 Fix ec_find_good for misplaced objects with extra corrupted chunks 2026-05-11 15:57:21 +03:00
Vitaliy Filippov 5100f822d8 Wrap all submit_primary_xxx_subops waits with if (n_subops) > 0 2026-05-11 15:17:50 +03:00
Vitaliy Filippov 7432494e88 Remove _LARGEFILE64_SOURCE warnings 2026-05-11 14:59:20 +03:00
Vitaliy Filippov d0c0f3ea39 Do not ignore cur_primary change on PG state validation errors 2026-05-11 14:59:20 +03:00
Vitaliy Filippov f61190f31d Fix send_list/outbox desync on failed SEC_READ_BMP (possible memory leak) 2026-05-11 14:59:11 +03:00
Vitaliy Filippov 3dc0ab5c33 Clear inconsistent_objects on repeer 2026-05-11 14:59:08 +03:00
Vitaliy Filippov de96efed2f Fix 2^(64-PG_EPOCH_BITS) - 1 check 2026-05-11 14:59:08 +03:00
Vitaliy Filippov 87a5230798 Add forgotten header_csum check to the new store 2026-05-11 14:59:08 +03:00
Vitaliy Filippov 0c5e6d4346 Do not use modified_block(ui32) to store lsn(ui64) 2026-05-11 14:59:08 +03:00
Vitaliy Filippov b278087410 Fix assert in blockstore_heap 2026-05-11 13:01:03 +03:00
Vitaliy Filippov a8e821b13b Mark OSDs as dirty during deletes more correctly 2026-05-11 12:56:23 +03:00
Vitaliy Filippov caa70317fa Add a workaround for "double claim" bug with the new store 2026-05-10 17:00:36 +03:00
Vitaliy Filippov b8eaaabfe4 Add raw-ls command for troubleshooting 2026-05-10 17:00:36 +03:00
Vitaliy Filippov e4d80c415e Always check pool resharding state also for stabilize/rollback 2026-05-10 13:22:02 +03:00
Vitaliy Filippov 553191c3ff Add asserts to the new store too O:-) 2026-05-10 11:39:54 +03:00
Vitaliy Filippov ab385252b5 Add offset asserts before each write 2026-05-10 10:56:32 +03:00
Vitaliy Filippov 041185c673 Release 3.0.11
A single hotfix:

- Fix missing error handling in EC snapshot/cloned image reads potentially
  leading to corrupted reads during OSD/PG restarts
2026-05-09 18:49:36 +03:00
Vitaliy Filippov b03ac80a57 Fix missing error handling in chained read sec_read_bmp handling 2026-05-07 15:01:49 +03:00
Vitaliy Filippov 2ba56074f9 Run copy_part_bitmaps only after all subops succeed (slightly more correct) 2026-05-07 15:01:40 +03:00
Vitaliy Filippov 4acfe149cb Release 3.0.10
Important bug fixes (new store):
- Fix OSDs possibly refusing to start with "write metadata failed at offset xxx: Invalid argument"
  (fix buffer alignment during initial garbage collection)
- Rollback change from 3.0.4 - on-disk garbage entries are not skipped on start again. This change
  doesn't have any impact normally, but OSDs originally running 3.0.0-3.0.2 and then upgraded
  to 3.0.9 may hit a bug where 3.0.9 refuses to start due to entries marked as garbage too
  early and flushed to disk in 3.0.0-3.0.2.

Other changes:
- Auto-select the only RDMA device/port if there is only one
- Rollback one 3.0.9 change - there was no actual use-after-free :)
  (the problem was only relevant to an unstable development version)
- Fix `vitastor-nfs --trace` option
- Fix an unintended 1 second sleep in vitastor-cli rm-data
- Fix inode statistics not being cleared for a deleted pool
- Fix print to stdout in client
2026-04-27 13:53:48 +03:00
Vitaliy Filippov 008ed5b269 Rollback 25ecca7625 - there was no actual use-after-free :) 2026-04-22 01:35:38 +03:00
Vitaliy Filippov 4fffe0f032 Fix vitastor-nfs trace option 2026-04-20 21:51:47 +03:00
139 changed files with 5245 additions and 2757 deletions
+36
View File
@@ -720,6 +720,24 @@ jobs:
echo ""
done
test_snapshot_chain_enc_gcm:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: TEST_NAME=enc_gcm ENCRYPTED=1 VITASTOR_CFG=',"proto_checksums":"gcm"' /root/vitastor/tests/test_snapshot_chain.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_snapshot_chain:
runs-on: ubuntu-latest
needs: build
@@ -1278,6 +1296,24 @@ jobs:
echo ""
done
test_heal_ec_rdma:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: TEST_NAME=ec_rdma RDMA=1 SCHEME=ec /root/vitastor/tests/test_heal.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_checksum:
runs-on: ubuntu-latest
needs: build
+1 -1
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
set(VITASTOR_VERSION "3.0.9")
set(VITASTOR_VERSION "3.0.12")
include(CTest)
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.9
VITASTOR_VERSION ?= v3.0.12
all: build push
+1 -1
View File
@@ -49,7 +49,7 @@ spec:
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: vitalif/vitastor-csi:v3.0.9
image: vitalif/vitastor-csi:v3.0.12
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -121,7 +121,7 @@ spec:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
image: vitalif/vitastor-csi:v3.0.9
image: vitalif/vitastor-csi:v3.0.12
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -5,7 +5,7 @@ package vitastor
const (
vitastorCSIDriverName = "csi.vitastor.io"
vitastorCSIDriverVersion = "3.0.9"
vitastorCSIDriverVersion = "3.0.12"
)
// Config struct fills the parameters of request or user input
+1 -1
View File
@@ -1,4 +1,4 @@
vitastor (3.0.9-1) unstable; urgency=medium
vitastor (3.0.12-1) unstable; urgency=medium
* Bugfixes
+6 -8
View File
@@ -12,20 +12,18 @@ ARG REL=
WORKDIR /root
RUN set -e -x; \
if [ "$REL" = "buster" ]; then \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
fi; \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
RUN apt-get update && \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
apt-get -y build-dep fio && \
apt-get --download-only source fio
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.9
VITASTOR_VERSION ?= v3.0.12
all: build push
@@ -12,12 +12,7 @@ EnvironmentFile=/etc/vitastor/etcd.conf
SyslogIdentifier=etcd
ExecStart=bash -c 'docker run --rm -i -v /var/lib/vitastor/etcd:/data \
--log-driver none --network host $CONTAINER_OPTIONS --name vitastor-etcd \
$ETCD_IMAGE /usr/local/bin/etcd --name "$ETCD_NAME" --data-dir /data \
--snapshot-count 10000 --advertise-client-urls http://$ETCD_IP:2379 --listen-client-urls http://$ETCD_IP:2379 \
--initial-advertise-peer-urls http://$ETCD_IP:2380 --listen-peer-urls http://$ETCD_IP:2380 \
--initial-cluster-token vitastor-etcd-1 --initial-cluster "$ETCD_INITIAL_CLUSTER" \
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \
--auto-compaction-retention=10 --auto-compaction-mode=revision'
$ETCD_IMAGE /usr/local/bin/etcd --data-dir /data
ExecStop=docker stop vitastor-etcd
Restart=always
StartLimitInterval=0
+1 -1
View File
@@ -4,7 +4,7 @@
#
# Desired Vitastor version
VITASTOR_VERSION=v3.0.9
VITASTOR_VERSION=v3.0.12
# Additional arguments for all containers
# For example, you may want to specify a custom logging driver here
-3
View File
@@ -1,4 +1 @@
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
ETCD_NAME=""
ETCD_IP=""
ETCD_INITIAL_CLUSTER=""
+4 -4
View File
@@ -25,7 +25,7 @@ Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't su
- [vault_timeout_ms](#vault_timeout_ms)
- [vault_error_timeout_sec](#vault_error_timeout_sec)
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
- [max_cipher_pool_size](#max_cipher_pool_size)
## etcd_client_cert
@@ -141,10 +141,10 @@ Time (in seconds) to wait before retrying after receiving an error from Vault.
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
in case of system clock drift.
## max_aes_xts_pool_size
## max_cipher_pool_size
- Type: integer
- Default: 256
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
doesn't require modification.
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
for each cipher and for encryption/decryption. Probably doesn't require modification.
+4 -4
View File
@@ -27,7 +27,7 @@ OSD, мониторами и клиентами.
- [vault_timeout_ms](#vault_timeout_ms)
- [vault_error_timeout_sec](#vault_error_timeout_sec)
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
- [max_cipher_pool_size](#max_cipher_pool_size)
## etcd_client_cert
@@ -145,10 +145,10 @@ OSD, клиенты и мониторы должны иметь разные п
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
## max_aes_xts_pool_size
## max_cipher_pool_size
- Тип: целое число
- Значение по умолчанию: 256
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
Вряд ли требует изменения.
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
+5 -5
View File
@@ -120,12 +120,12 @@
info_ru: |
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
- name: max_aes_xts_pool_size
- name: max_cipher_pool_size
type: int
default: 256
info: |
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
doesn't require modification.
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
for each cipher and for encryption/decryption. Probably doesn't require modification.
info_ru: |
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
Вряд ли требует изменения.
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
+2 -2
View File
@@ -26,9 +26,9 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
The instruction is very simple.
1. Download a Docker image of the desired version: \
`docker pull vitalif/vitastor:v3.0.9`
`docker pull vitalif/vitastor:v3.0.12`
2. Install scripts to the host system: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
3. Reload udev rules: \
`udevadm control --reload-rules`
4. Enable the vitastor-host service: \
+2 -2
View File
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
Инструкция по установке максимально простая.
1. Скачайте Docker-образ желаемой версии: \
`docker pull vitalif/vitastor:v3.0.9`
`docker pull vitalif/vitastor:v3.0.12`
2. Установите скрипты в хост-систему командой: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
3. Перезагрузите правила udev: \
`udevadm control --reload-rules`
4. Включите сервис vitastor-host: \
-1
View File
@@ -17,7 +17,6 @@
- CMake
- jerasure, c-ares headers and libraries
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
- tcmalloc (google-perftools-dev)
## Basic instructions
-1
View File
@@ -17,7 +17,6 @@
- CMake
- Заголовки и библиотеки jerasure, c-ares
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
- tcmalloc (google-perftools-dev)
## Базовая инструкция
+439
View File
@@ -0,0 +1,439 @@
[Документация](../../README-ru.md#документация) → Безопасность
-----
[Read in English](security.en.md)
# Оглавление
⚠️ Предупреждение: детальное описание настроек безопасности достаточно длинное.
Если не боитесь - читайте [Подробное описание](#подробное-описание).
Если хотите просто быстро настроить Vitastor с шифрованием - читайте начало статьи.
- [Быстрая настройка](#быстрая-настройка)
-
# Быстрая настройка
# Пользовательские сценарии
Зачем всё это нужно вам?
# Подробное описание
Начиная с версии 3.1.0, в Vitastor есть следующие функции:
1. Шифрование соединений с etcd (TLS)
2. Шифрование соединений с OSD (AES-GCM) - по выбору либо только заголовков, либо и заголовков, и данных
3. Сквозное шифрование данных образов (AES-XTS)
4. Хранения ключей шифрования AES-XTS во внешнем Vault
5. Контрольных сумм данных на транспортном уровне с секретной "солью"
6. Аутентификация с помощью TLS (X.509) сертификатов и закрытых ключей
7. Разграничение прав доступа клиентов к данным etcd
8. Разграничение прав доступа клиентов к данным самих образов (на стороне OSD)
По умолчанию шифрование, аутентификация и авторизация отключены, но, начиная с 3.1.0,
используются контрольные суммы данных на транспортном уровне (`proto_checksums=payload`).
## Шифрование соединений с etcd (TLS)
Варианты настройки:
- Без шифрования (http)
- С шифрованием (https)
- С клиентским сертификатом, но при выключенной авторизации (`use_auth=false`) - используется
отдельный сертификат и ключ: `etcd_client_cert`, `etcd_client_key`
- С клиентским сертификатом, при включённой аутентификации на уровне OSD - используется общий
сертификат и ключ: для OSD - `osd_cert` и `osd_pkey`, для клиентов - `cert` и `pkey`
## Шифрование соединений с OSD (AES-GCM)
Варианты настройки:
- Без шифрования и без контрольных сумм: `proto_checksums=none`.
- Без шифрования, с контрольными суммами данных: `proto_checksums=payload` (можно не указывать,
т.к. это значение по умолчанию). При этом контрольные суммы можно отключить на стороне
клиента либо использовать более старые версии клиента, не поддерживающие контрольные суммы.
Если нужно запретить подключение клиентов без контрольных сумм, можно использовать опцию
`force_proto_checksums=payload`.
- С шифрованием заголовков и контрольными суммами данных: активируется при установленных опциях
`cert`, `pkey`, `osd_ca` на стороне клиента и `osd_cert`, `osd_pkey`, `osd_ca`, `client_ca`
на стороне OSD, при `proto_checksums=payload`. При этом по умолчанию запрещается
отключение контрольных сумм на уровне клиента, то есть используется `force_proto_checksums=payload`.
- С полным шифрованием всего трафика: аналогично прошлому варианту, но с `proto_checksums=gcm`.
Клиенту при этом по умолчанию разрешается понизить уровень защиты до контрольных сумм, но
это тоже можно запретить через `force_proto_checksums=gcm`. Данный вариант не является рекомендуемым,
так как добавлен в первую очередь для возможной поддержки небезопасных (публичных) сетей и
больше всего снижает производительность. В частности, если одновременно использовать полное
шифрование трафика и сквозное шифрование образов AES-XTS, то данные будут шифроваться дважды.
Для шифрования используется алгоритм AES-256-GCM и собственный упрощённый протокол согласования
ключей, полностью аналогичный TLS 1.3 ECDHE.
## Сквозное шифрование данных образов (AES-XTS)
Клиент Vitastor поддерживает шифрование данных каждого образа своим ключом. В этом случае на OSD
уходят уже зашифрованные данные и сами OSD не видят настоящее содержимое образов. Разные ключи
в том числе могут иметь разные снимки или клоны одного и того же образа. Например, можно сделать
базовый образ ВМ (условный Debian Linux) нешифрованным, но наследовать от него шифрованные образы
клиентских ВМ.
Ключи шифрования образов могут храниться либо в etcd, либо во внешнем Vault. Во втором случае
в etcd хранятся только ID ключей, а Vitastor вообще не имеет доступа к данным образов. Для
использования Vault нужно создать образ с опцией `--enc_key vault:ID`, а в конфигурации указать
опции:
- vault_url
- vault_ca
- vault_client_cert
- vault_client_key
Ещё раз повторимся, что если AES-XTS используется с полным шифрованием трафика (`proto_checksums=gcm`),
то данные образов шифруются дважды - сначала AES-XTS, а потом AES-GCM. Можете использовать,
только если вы совсем параноик :-).
## Производительность шифрования
У вас может возникнуть вопрос - а как быстро всё это прекрасное шифрование работает?
Ответ - скорость сильно зависит от процессора. Складывается она из нескольких вещей:
-
TODO: vitastor-cli bench.
## Аутентификация по сертификатам
При включённом шифровании клиенты, OSD и мониторы Vitastor аутентифицируются по сертификатам
как при соединениях с etcd (Antietcd), так и с OSD.
Для OSD и мониторов должны использоваться отдельные сертификаты - либо самоподписанные, либо
подписанные отдельными CA (`osd_ca` и `mon_ca`). При этом все OSD могут использовать один и
тот же сертификат и все мониторы тоже могут использовать один и тот же сертификат, так как
привилегии разных OSD или разных мониторов ничем не отличаются (теоретически можно было бы
сделать разграничение сертификатов OSD по пулам, но пока что такой необходимости не было).
Также сертификат монитора может быть вообще не нужен, если Antietcd встраивается в сам монитор.
В этом случае монитор и так имеет доступ ко всем данным etcd прямо в памяти.
Каждый клиент должен иметь свой сертификат, подписанный общим корневым сертификатом
для клиентов (`client_ca`). Common Name сертификата должно равняться имени пользователя.
## Модель прав доступа
Привилегии пользователей хранятся в данных etcd в ключах `/vitastor/config/user/<имя>`.
У пользователя есть 2 свойства:
- Тип:
- Клиент (`type=client` или не указано) - может читать и модифицировать только явным образом
разрешённые образы.
- Администратор (`type=admin`) - может читать и модифицировать все образы, а также администрировать
кластер: смотреть общую статистику и состояние, создавать и удалять OSD и так далее.
- Список имён групп, членом которых пользователь является.
У образов есть 3 свойства:
- Владелец (owner) - имя пользователя, которому разрешено и читать, и менять образ
- Группа владельцев (owner_group) - имя группы владельцев
- Группа читатетей (reader_group) - имя группы пользователей, которым разрешено читать образ
У пулов есть 1 свойство:
- Группа создателей (creator_group) - имя группы пользователей, которым разрешено создавать образы в пуле
## Права доступа к данным etcd
Привилегии реализуются через Antietcd во всех режимах работы. Если используется etcd, то
Antietcd выступает в роли фильтрующего прокси, при этом он может быть встроен в монитор
Vitastor или запущен отдельно. В этом случае etcd должен разрешать входящие подключения
только от Antietcd, а все остальные компоненты должны соединяться с Antietcd.
Если же используется Antietcd, то привилегии реализуются в нём самом.
Если используется встроенный в монитор Antietcd, то привилегии включаются либо параметром
`use_auth: true`, либо, если этот параметр не указан - включается автоматически, если задан
любой из параметров `client_ca`, `osd_ca`, `mon_ca`. При этом монитор требует указания
параметров `client_ca` и `osd_ca`, а если не используется режим проксирования в etcd -
также `antietcd_server_ca`, чтобы Antietcd мог отличать кластерные соединения от клиентских.
Если используется отдельно стоящий Antietcd, привилегии нужно включать явным образом.
Встроенные привилегии etcd не поддерживаются по причине их многочисленных недоработок:
- Аутентификация по сертификатам не работает в REST интерфейсе etcd,
- Привилегии хранятся отдельно от k/v и не могут участвовать в транзакциях,
- Менять привилегии может только администратор (root)
- Нет поддержки фильтрации ответов чтения по привилегиям.
Подробный список привилегий на ключи в etcd [смотрите ниже](#привилегии-etcd).
## Права доступа к данным OSD
Регулируется опцией `use_auth`, либо, если она не указана, включается автоматически,
если используется шифрование, то есть, если заданы опции `osd_ca` и `client_ca`.
OSD аутентифицирует клиентов по сертификатам и разрешает каждому клиенту только
то, что ему разрешено согласно модели прав доступа.
Подробный список разрешаемых OSD операций [смотрите ниже](#привилегии-osd).
## Права доступа к API
[vitastor-cli serve](../usage/cli.ru.md#serve) также поддерживает клиентскую
аутентификацию по сертификатам. Принимаются только сертификаты, подписанные
`client_ca`. В качестве серверного сертификата используется отдельный сертификат
`server_cert` с ключом `server_key`.
При этом для корректной работы `vitastor-cli serve` он сам должен использовать
для доступа в Vitastor сертификат (`cert`+`pkey`) пользователя с правами
администратора (`type=admin`).
Обычным клиентам при доступе к API разрешаются только API-операции с образами,
доступными им либо на чтение (для чтения), либо на запись (для модификации).
Все остальные API-вызовы разрешаются только для администраторов.
Подробный список разрешаемых API операций [смотрите ниже](#привилегии-api).
## Привилегии etcd
Ниже все названия ключей приведены без общего префикса `/vitastor`.
Разрешённые операции с ключами в Antietcd для клиентов (`type=client`):
- Только чтение:
- Разрешено всегда:
- `/config/global`
- `/config/node_placement`
- `/config/pools`
- `/pg/config`
- `/osd/state/*`
- `/pg/state/*`
- `/index/maxid/*`
- Для образов, которые [может читать пользователь](#модель-прав-доступа):
- `/config/inode/*`
- `/index/image/*`
- `/inode/stats/*`
- Чтение и запись:
- Для пулов, в которых может создавать образы пользователь:
- `/index/maxid/*`
- Для образов, которыми владеет пользователь:
- `/config/inode/*`
- `/index/image/*`
Разрешённые операции с ключами в Antietcd для администраторов (`type=admin`):
- Чтение:
- `/stats`
- `/mon/*`
- `/pg/*`
- `/pgstats/*`
- `/inode/stats/*`
- `/pool/stats/*`
- Чтение и запись:
- `/config/*`
- `/osd/*`
- `/index/*`
- `/pg/history/*`
Разрешённые операции с ключами в etcd для OSD:
- Чтение:
- `/pg/config`
- `/config/*`
- Чтение и запись:
- `/osd/*`
- `/pg/state/*`
- `/pg/history/*`
- `/pgstats/*`
Разрешённые операции с ключами в etcd для мониторов:
- Чтение:
- `/config/*`
- `/osd/*`
- `/pgstats/*`
- Чтение и запись:
- `/pg/config`
- `/stats`
- `/history/last_clean_pgs`
- `/mon/*`
- `/pg/history/*`
- `/inode/stats/*`
- `/pool/stats/*`
## Привилегии OSD
Клиентские операции:
- READ - разрешено для образов, доступных пользователю на чтение.
- WRITE, DELETE, SCRUB - разрешены для образов, доступных пользователю на запись.
- SYNC - операция не связана с образом и разрешена всегда.
- DESCRIBE - операция разрешена только для администраторов (используются командами
`vitastor-cli describe` и `fix`).
- PING - операция разрешена всегда.
- SHOW_CONFIG - операция разрешена всегда, однако если в ней клиент представляется
как OSD, то проверяется, что он использует сертификат, подписанный `osd_ca`.
- SEC_LIST (листинг) - разрешена другим OSD и администраторам с любыми параметрами,
а обычным клиентам разрешена только для запросов, ограниченных образом, доступным
пользователю на чтение.
Кластерные операции - разрешаются только другим OSD:
- SEC_READ
- SEC_WRITE
- SEC_WRITE_STABLE
- SEC_SYNC
- SEC_STABILIZE
- SEC_ROLLBACK
- SEC_DELETE
- SEC_READ_BMP
- SEC_LOCK
## Привилегии API
Клиентам (пользователям с `type=client`) разрешаются операции:
- image/list - для образов, которые пользователь может читать.
- image/create - для пулов, в которых пользователю разрешено создавать образы, либо
для создания снимков образов, которыми пользователь владеет.
- image/delete, image/flatten, image/modify - для образов, которыми пользователь владеет.
Все остальные операции разрешаются только администраторам (`type=admin`).
Таким образом, доступны следующие варианты настройки:
### Mon в роли Etcd proxy
Mon
- use_antietcd: true
- etcd_proxy = {
urls: [],
cert = <antietcd.pem>,
key,
ca = <etcd.pem>,
}
- antietcd_cert = antietcd.pem
- antietcd_key
etcd
--client-cert-auth --cert-file=etcd.pem --key-file=etcd.key --trusted-ca-file=antietcd.pem \
--peer-client-cert-auth --peer-cert-file=etcd.pem --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.pem
### Mon с отдельным Antietcd Proxy
Mon
- use_antietcd: false
- etcd_ca = antietcd.pem
Antietcd
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
--cert antietcd.pem --key antietcd.key --ca client_ca.pem --osd_ca osd_ca.pem \
--etcd_cert antietcd.pem --etcd_key antietcd.key --etcd_ca etcd.pem
etcd
--client-cert-auth --cert-file=etcd.pem --key-file=etcd.key --trusted-ca-file=antietcd.pem \
--peer-client-cert-auth --peer-cert-file=etcd.pem --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.pem
### Mon со встроенным Antietcd
Mon
- use_antietcd: true
- use_auth: true
- antietcd_cert = antietcd.pem
- antietcd_key
### Отдельный Antietcd
Mon
- use_antietcd: false
- etcd_ca = antietcd.pem
Antietcd
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js
## Варианты настройки
### Настройка по умолчанию
Используются только контрольные суммы данных на транспортном уровне. Соединения с etcd не шифруются.
Аутентификация и авторизация не используется, любой клиент имеет доступ ко всем данным кластера.
Аналог настройки:
- proto_checksums: payload
### Полная защита
Везде
- osd_ca
- client_ca
- etcd_ca = antietcd.pem
OSD
- osd_cert
- osd_pkey
Клиент
- cert
- pkey
### Только защита etcd
- etcd_ca
- etcd_cert
- etcd_key
### antietcd и только защита antietcd
- etcd_ca
- etcd_cert
- etcd_key
- use_antietcd: true
- antietcd_cert = etcd_ca
- antietcd_key
- antietcd_ca = etcd_cert
### Полное шифрование протокола, включая данные
Внимание: если включить этот вариант защиты и при этом
### Только контрольные суммы на транспортном уровне, без шифрования
## Настройка Vault/OpenBao
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
-new -newkey rsa:4096 -nodes -keyout vault.key -out vault.crt
bao status -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
bao operator unseal -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
bao auth enable -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 cert
bao secrets enable -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 -path=secret kv-v1
bao kv put -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
cat >testimg3.policy <<EOF
path "/secret/vitastor/testimg3" {
capabilities = ["read"]
}
EOF
bao policy write -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
bao write -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key --json '{}' https://vault:8200/v1/auth/cert/login
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key -H 'X-Vault-Token: s.Qkrm78BeK7Rqdz5MA3eJZNbu' https://vault:8200/v1/secret/vitastor/testimg3
+1
View File
@@ -262,3 +262,4 @@ Options:
| `--logfile <FILE>` | log to the specified file |
| `--enforce 1` | enforce permissions at the server side (no by default) |
| `--foreground 1` | stay in foreground, do not daemonize |
| `--trace` | trace all NFS requests |
+1
View File
@@ -274,3 +274,4 @@ VitastorFS из GPUDirect.
| `--logfile <FILE>` | записывать логи в заданный файл |
| `--enforce 1` | проверять права доступа на стороне сервера (по умолчанию нет) |
| `--foreground 1` | не уходить в фон после запуска |
| `--trace` | логгировать все запросы NFS |
+44 -14
View File
@@ -3,6 +3,7 @@
const AntiEtcd = require('antietcd');
const vitastor_auth_filter = require('./vitastor_auth_filter.js');
const vitastor_persist_filter = require('./vitastor_persist_filter.js');
const { b64, local_ips } = require('./utils.js');
@@ -27,37 +28,66 @@ class AntiEtcdAdapter
is_local['::'] = true;
is_local[''] = true;
// split :, 3 -> <schema>:<//ip>:<port>
const selected = cluster.map(s => s.split(':', 3)).filter(ip => is_local[ip[1].substr(2)] && (!cfg_port || ip[2] == cfg_port));
const cluster_local = cluster.map(s =>
{
const m = /^https?:\/\/(?:\[(.*)\]|([^\[\:]+))(?::(\d+))?$/.exec(s);
return [ m[2] || m[1], m[3] || 2379 ];
});
const selected = cluster_local.filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
if (selected.length > 1)
{
console.error('More than 1 etcd_address matches local IPs, please specify port');
console.error('More than 1 etcd_address matches local IPs: '+(selected.join(', '))+', please specify port');
process.exit(1);
}
else if (selected.length == 1)
{
const antietcd_config = {
ip: selected[0][1].substr(2),
port: selected[0][2],
ip: selected[0][0],
port: selected[0][1],
cert: config.antietcd_cert,
key: config.antietcd_key,
ca: config.etcd_ca,
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][2]+'.json.gz'),
ca: config.antietcd_ca,
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
node_id: selected[0][1].substr(2)+':'+selected[0][2], // node_id = ip:port
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
cluster_key: (config.etcd_prefix || '/vitastor'),
stale_read: 1,
log_level: 1,
};
if (config.use_auth)
if (config.etcd_proxy)
{
// Monitor may use the builtin etcd_proxy mode
if (!config.etcd_proxy.urls)
{
console.error('etcd_proxy.urls are empty');
process.exit(1);
}
antietcd_config.etcd_proxy = config.etcd_proxy.urls;
antietcd_config.etcd_cert = config.etcd_proxy.cert;
antietcd_config.etcd_key = config.etcd_proxy.key;
antietcd_config.etcd_ca = config.etcd_proxy.ca;
delete antietcd_config.data;
delete antietcd_config.persist_filter;
delete antietcd_config.cluster;
delete antietcd_config.cluster_key;
}
const use_auth = config.use_auth || config.use_auth == null && config.client_ca;
if (use_auth)
{
antietcd_config.client_cert_auth = true;
antietcd_config.auth_filter = require('./vitastor_auth_filter.js');
antietcd_config.peer_ca = config.antietcd_server_ca;
if (!config.antietcd_server_ca || config.antietcd_server_ca == config.etcd_ca)
antietcd_config.auth_filter = vitastor_auth_filter;
antietcd_config.ca = config.client_ca;
antietcd_config.osd_ca = config.osd_ca;
antietcd_config.mon_ca = config.mon_ca;
if (!config.etcd_proxy)
{
console.error('Secure setup requires separate antietcd_server_ca (for signing antietcd server certificates) and etcd_ca (for signing client certificates)');
process.exit(1);
antietcd_config.peer_ca = config.antietcd_server_ca;
if (!config.antietcd_server_ca || config.antietcd_server_ca == config.client_ca)
{
console.error('Secure setup requires separate antietcd_server_ca (for signing antietcd server certificates) and client_ca (for signing client certificates)');
process.exit(1);
}
}
}
for (const key in config)
@@ -184,7 +214,7 @@ class AntiEtcdAdapter
await new Promise(ok => setTimeout(ok, timeout-(Date.now()-prev)));
}
prev = Date.now();
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { username: 'root' });
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { user_type: 'mon' });
if (res.error)
{
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
+2 -2
View File
@@ -627,7 +627,7 @@ class Mon
if (this.state.pg.history[pool_id] &&
this.state.pg.history[pool_id][pg])
{
pg_history[pg-1] = this.state.pg.history[pool_id][pg];
pg_history[pg-1] = JSON.parse(JSON.stringify(this.state.pg.history[pool_id][pg]));
}
}
const real_prev_pgs = [];
@@ -719,7 +719,7 @@ class Mon
this.next_recheck_timer = null;
this.next_recheck_at = 0;
this.schedule_recheck();
}, now-this.next_recheck_at);
}, (this.next_recheck_at-now)*1000);
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor-mon",
"version": "3.0.9",
"version": "3.0.12",
"description": "Vitastor SDS monitor service",
"main": "mon-main.js",
"scripts": {
+2 -2
View File
@@ -84,7 +84,7 @@ function scale_pg_history(prev_pg_history, prev_pgs, new_pgs)
finish_pg_history(merged_history[1]);
for (let i = 0; i < new_pg_count; i++)
{
new_pg_history[i] = { ...merged_history[1] };
new_pg_history[i] = JSON.parse(JSON.stringify(merged_history[1]));
}
}
// Mark history keys for removed PGs as removed
@@ -102,7 +102,7 @@ function scale_pg_count(prev_pgs, new_pg_count)
{
for (let i = prev_pgs.length; i < new_pg_count; i++)
{
prev_pgs[i] = prev_pgs[i % prev_pgs.length];
prev_pgs[i] = [ ...prev_pgs[i % prev_pgs.length] ];
}
}
else if (prev_pgs.length > new_pg_count)
+327 -44
View File
@@ -1,38 +1,278 @@
#!/usr/bin/node
// Simple systemd unit generator for etcd
// Simple Vitastor etcd / antietcd / TLS configurator
// Copyright (c) Vitaliy Filippov, 2019+
// License: MIT
// USAGE:
// 1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes
// 2) Run ./make-etcd.js. It will create the etcd service on one of specified IPs
const child_process = require('child_process');
const fs = require('fs');
const os = require('os');
const readline = require('readline');
run().catch(e => { console.error(e); process.exit(1); });
const help_text = `Initialize a Vitastor cluster (etcd, vitastor.conf and TLS certificates)
(c) Vitaliy Filippov, 2019+ (MIT)
USAGE:
1) Create a minimal vitastor.conf with etcd_address, osd_network and (optionally) use_auth.
Example: {"etcd_address":["http://10.0.0.10:2379","http://10.0.0.11:2379","http://10.0.0.12:2379"],"use_auth":false,"osd_network":"10.0.0.0/24"}
Or: {"etcd_address":["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],"use_auth":true,"osd_network":"10.0.0.0/24"}
2) Run: ${process.argv[1]} [./vitastor.conf]
You can run it on etcd/monitor nodes or on an external node.
It configures etcd, generates TLS certificates (on the first or external node), copies them
to other etcd/monitor nodes, and updates vitastor.conf with TLS options.
3) If you have OSD-only nodes, run:
${process.argv[1]} --copy-to-osd-node NODE_NAME ./vitastor.conf
It copies vitastor.conf and required TLS certificates to that node.
OPTIONS:
--gen-certs
force certificate generation even if it's not the first node
--no-certs
disable certificate generation
--no-copy
do not copy initial certificates to other nodes
--copy-to-osd-node NODE[,NODE2,...]
copy vitastor.conf and TLS certificates required for OSDs to NODES using scp
--copy-to-mon-node NODE[,NODE2,...]
copy vitastor.conf and TLS certificates required for monitor and etcd to NODES using scp
--copy-to-client-node NODE[,NODE2,...]
copy vitastor.conf and TLS certificates required for clients to NODES using scp
`;
async function run()
{
const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
if (config_path == '-h' || config_path == '--help')
let config_path = '/etc/vitastor/vitastor.conf';
let config_dir = '/etc/vitastor/';
let gen_certs = 'auto';
let copy_initial = true;
let copy_to_osd =
for (let i = 2; i < process.argv.length; i++)
{
console.log(
'Initialize systemd etcd service for Vitastor\n'+
'(c) Vitaliy Filippov, 2019+ (MIT)\n'+
'\n'+
'USAGE:\n'+
'1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes\n'+
'2) Run '+process.argv[1]+' [config_path]\n'
);
process.exit(0);
if (arg == '-h' || arg == '--help')
{
console.log(help_text);
process.exit(0);
}
else if (arg == '--only-certs')
{
i++;
gen_certs =
}
else if (arg == '--copy')
{
i++;
copy = process.argv[i];
if (copy !== 'ask' && copy !== 'yes' && copy !== 'no')
{
console.error('--copy should be "ask", "yes" or "no"');
process.exit(1);
}
}
else if (arg[0] == '-')
{
console.error('Unknown option: '+arg[0]);
process.exit(1);
}
else
{
config_path = arg;
}
}
if (!fs.existsSync(config_path))
{
console.log(config_path+' is missing');
process.exit(1);
}
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
if (!config.etcd_address)
{
console.log("etcd_address is missing in "+config_path);
process.exit(1);
}
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
.map(s => /^(https?):\/\/(\[[^\]]+\]|[^\[\]\:\/]+)(?::(\d+))?/.exec(s.toLowerCase()))
.filter(s => s)
.map(s => ({
scheme: s[1],
addr: s[2].indexOf(':') && s[2][0] != '[' ? '['+s[2]+']' : s[2],
ip: s[2][0] == '[' ? s[2].substr(1, s[2].length-2) : s[2],
port: s[3],
}));
const tls = etcds.filter(e => e.scheme === 'https').length > 0;
const use_auth = tls && config.use_auth;
const num = select_local_etcd(etcds);
if (tls)
{
if (gen_certs === 'yes')
{
gen_certs = true;
console.log('Certificate generation is requested explicitly, generating');
}
else if (gen_certs === 'no')
{
gen_certs = false;
console.log('Certificate generation is disabled explicitly, skipping');
}
else if (num < 0)
{
gen_certs = true;
console.log('No matching IPs in etcd_address from '+config_path+', only generating certificates');
}
else if (fs.existsSync("/etc/vitastor/etcd.crt"))
{
gen_certs = false;
console.log('/etc/vitastor/etcd.crt already exists, assuming certificates are already generated');
}
else if (num === 0)
{
gen_certs = true;
console.log('This is monitor node 1, generating certificates');
}
else
{
console.log('This is monitor node '+(num+1)+', /etc/vitastor/etcd.crt does not exist, please copy certificates to this node');
process.exit(1);
}
if (gen_certs)
{
if (copy === 'ask')
copy = await ask_copy('Copy certificates and vitastor.conf to other nodes after generation?');
copy = (copy === 'y' || copy === 'yes');
await make_certs(dir, copy);
}
await write_auth_config(config, config_path);
}
if (num < 0)
{
console.log('No matching IPs in etcd_address from '+config_path);
process.exit(tls && gen_certs ? 0 : 1);
}
await configure_etcd();
process.exit(0);
}
async function ask_copy(question)
{
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '> ',
});
let copy;
while (true)
{
copy = await new Promise(ok => rl.question(question, ok));
if (copy != 'y' && copy != 'n' && copy != 'yes' && copy != 'no')
console.log('Please type "yes" or "no"');
else
break;
}
return copy;
}
async function make_certs(dir, copy)
{
console.log(`-----
Generating certificates in ${dir}
-----
`);
await make_ca("/O=Vitastor etcd CA", dir+"etcd_ca");
await make_signed("/CN=Vitastor etcd", dir+"etcd", dir+"etcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
if (use_auth)
{
await make_ca("/O=Vitastor Antietcd CA", dir+"antietcd_ca");
await make_signed("/CN=Vitastor Antietcd", dir+"antietcd", dir+"antietcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
await make_ca("/CN=Vitastor OSD", dir+"osd");
await make_ca("/O=Vitastor Client CA", dir+"client_ca");
await make_signed("/CN=admin", dir+"admin", dir+"client_ca");
}
if (use_auth)
{
console.log(`-----
Certificates generated, commands to copy them:
- Monitor+OSD node:
cd ${dir} && scp antietcd_ca.crt antietcd.crt antietcd.key osd.crt osd.key client_ca.crt etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
- Monitor node:
cd ${dir} && scp antietcd_ca.crt antietcd.crt antietcd.key osd.crt client_ca.crt etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
- OSD node:
cd ${dir} && scp antietcd_ca.crt osd.crt osd.key client_ca.crt root@NODE:/etc/vitastor/
-----
`);
}
else
{
console.log(`-----
Certificates generated, commands to copy them:
- Monitor node:
cd ${dir} && scp etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
-----
`);
}
if (copy)
{
const to_copy = use_auth
? [ "antietcd_ca.crt", "antietcd.crt", "antietcd.key", "osd.crt", "osd.key", "client_ca.crt", "etcd_ca.crt", "etcd.crt", "etcd.key" ]
: [ "etcd_ca.crt", "etcd.crt", "etcd.key" ];
for (const node of etcds)
{
await system("scp "+dir+to_copy.join(" "+dir)+" root@"+node.ip+"/etc/vitastor/");
}
}
else
{
console.warn('Certificates generated in /etc/vitastor, please copy them to other nodes');
}
}
async function write_auth_config(config, config_path)
{
const auth = {};
if (use_auth)
{
auth["use_antietcd"] = true;
auth["etcd_proxy"] = {
urls: etcds.map(e => e.ip+':2381'),
cert: "/etc/vitastor/antietcd.crt",
key: "/etc/vitastor/antietcd.key",
ca: "/etc/vitastor/etcd_ca.crt",
};
auth["antietcd_cert"] = "/etc/vitastor/antietcd.crt";
auth["antietcd_key"] = "/etc/vitastor/antietcd.key";
auth["etcd_ca"] = "/etc/vitastor/antietcd_ca.crt";
auth["osd_cert"] = "/etc/vitastor/osd.crt";
auth["osd_pkey"] = "/etc/vitastor/osd.key";
auth["osd_ca"] = "/etc/vitastor/osd.crt";
auth["client_ca"] = "/etc/vitastor/client_ca.crt";
auth["cert"] = "/etc/vitastor/admin.crt";
auth["pkey"] = "/etc/vitastor/admin.key";
}
else
{
auth["etcd_ca"] = "/etc/vitastor/etcd.crt";
}
for (const k in auth)
{
if ((k in config) && JSON.stringify(auth[k]) != JSON.stringify(config[k]))
{
// Auth options already overridden with non-default
console.log(k+" is already overridden in "+config_path+", skipping config update");
return;
}
}
for (const k in auth)
{
config[k] = auth[k];
}
console.log(`-----
Updating ${config_path}
-----
`);
fs.writeFileSync(config_path, JSON.stringify(config, 0, 4));
}
async configure_etcd()
{
const in_docker = fs.existsSync("/etc/vitastor/etcd.conf") &&
fs.existsSync("/etc/vitastor/docker.conf");
if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service"))
@@ -45,30 +285,49 @@ async function run()
console.log("/etc/systemd/system/etcd.service already exists");
process.exit(1);
}
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
if (!config.etcd_address)
const etcd_url = etcds[num].scheme + '://' + etcds[num].addr;
const options = {
name: 'etcd'+etcds[num].ip.replace(/[^0-9a-z_]/ig, '_'),
advertise_client_urls: etcd_url+':'+(use_auth ? 2381 : 2379),
listen_client_urls: etcd_url+':'+(use_auth ? 2381 : 2379),
initial_advertise_peer_urls: etcd_url+':2380',
listen_peer_urls: etcd_url+':2380',
initial_cluster_token: 'vitastor-etcd-1',
initial_cluster_state: 'new',
initial_cluster: etcds.map(e => `etcd${e.ip.replace(/[^0-9a-z_]/ig, '_')}=${e.scheme}://${e.addr}:2380`).join(','),
snapshot_count: 10000,
max_txn_ops: 100000,
max_request_bytes: 104857600,
auto_compaction_retention: 10,
auto_compaction_mode: 'revision',
};
if (tls)
{
console.log("etcd_address is missing in "+config_path);
process.exit(1);
options['cert_file'] = '/etc/vitastor/etcd.crt';
options['key_file'] = '/etc/vitastor/etcd.key';
if (use_auth)
{
options['client_cert_auth'] = '1';
options['trusted_ca_file'] = '/etc/vitastor/antietcd.crt';
}
options['peer_cert_file'] = '/etc/vitastor/etcd.crt';
options['peer_key_file'] = '/etc/vitastor/etcd.key';
if (use_auth)
{
options['peer_client_cert_auth'] = '1';
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd.crt';
}
}
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
.map(s => (''+s).replace(/^https?:\/\/|(:\d+)?(\/.*)?$/g, '').replace(/^\[(.*)\]$/, '$1').toLowerCase());
const num = select_local_etcd(etcds);
if (num < 0)
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
? fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' })
: "";
for (const k in options)
{
console.log('No matching IPs in etcd_address from '+config_path);
process.exit(0);
etcd_conf = replace_env(etcd_conf, 'ETCD_'+k.toUpperCase().replace(/-/, '_'), options[k]);
}
const etcd_url = 'http://' + (etcds[num].indexOf(':') >= 0 ? '['+etcds[num]+']' : etcds[num]);
const etcd_name = 'etcd'+etcds[num].replace(/[^0-9a-z_]/ig, '_');
const etcd_cluster = etcds.map(e => `etcd${e.replace(/[^0-9a-z_]/ig, '_')}=http://${e.indexOf(':') >= 0 ? '['+e+']' : e}:2380`).join(',');
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
if (in_docker)
{
let etcd_conf = fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' });
etcd_conf = replace_env(etcd_conf, 'ETCD_NAME', etcd_name);
etcd_conf = replace_env(etcd_conf, 'ETCD_IP', etcds[num]);
etcd_conf = replace_env(etcd_conf, 'ETCD_INITIAL_CLUSTER', etcd_cluster);
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
console.log('etcd for Vitastor configured. Run `systemctl enable --now vitastor-etcd` to start etcd');
process.exit(0);
}
@@ -83,12 +342,8 @@ Wants=network-online.target local-fs.target time-sync.target
[Service]
Restart=always
Environment=GOGC=50
ExecStart=etcd --name ${etcd_name} --data-dir /var/lib/etcd/vitastor \\
--snapshot-count 10000 --advertise-client-urls ${etcd_url}:2379 --listen-client-urls ${etcd_url}:2379 \\
--initial-advertise-peer-urls ${etcd_url}:2380 --listen-peer-urls ${etcd_url}:2380 \\
--initial-cluster-token vitastor-etcd-1 --initial-cluster ${etcd_cluster} \\
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \\
--auto-compaction-retention=10 --auto-compaction-mode=revision
EnvironmentFile=/etc/vitastor/etcd.conf
ExecStart=etcd --data-dir /var/lib/etcd/vitastor
WorkingDirectory=/var/lib/etcd/vitastor
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
User=etcd
@@ -106,7 +361,6 @@ WantedBy=multi-user.target
// Disable distribution etcd unit and enable our one
await system(`systemctl disable --now etcd`);
await system(`systemctl enable --now vitastor-etcd`);
process.exit(0);
}
function replace_env(text, key, value)
@@ -119,16 +373,29 @@ function replace_env(text, key, value)
function select_local_etcd(etcds)
{
const ifaces = os.networkInterfaces();
const local = {};
for (const ifname in ifaces)
{
for (const iface of ifaces[ifname])
for (let i = 0; i < etcds.length; i++)
if (etcds[i] == iface.address.toLowerCase())
return i;
{
const addr = iface.address;
if (iface.family == 'IPv6')
local[addr.toLowerCase()] = local['['+addr.toLowerCase()+']'] = true;
else
local[addr] = true;
}
}
for (let i = 0; i < etcds.length; i++)
{
if (local[etcds[i].addr])
return i;
}
return -1;
}
async function system(cmd)
{
console.log('Running '+cmd);
const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
let finish_cb;
cp.on('exit', () => finish_cb && finish_cb());
@@ -136,3 +403,19 @@ async function system(cmd)
await new Promise(ok => finish_cb = ok);
return cp.exitCode;
}
async function make_ca(subj, filename)
{
if (await system("openssl req -days 3650 -x509 -subj '"+subj+"' -addext basicConstraints=critical,CA:TRUE,pathlen:1"+
" -new -newkey rsa:4096 -nodes -keyout "+filename+".key -out "+filename+".crt"))
process.exit(1);
}
async function make_signed(subj, f, ca, san)
{
if (await system(`openssl req -subj '${subj}' ${san ? "-addext 'subjectAltName="+san+"'" : ""} -nodes -new -keyout ${f}.key -out ${f}.csr`))
process.exit(1);
if (await system(`openssl x509 -req -days 3650 -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
process.exit(1);
fs.unlinkSync(f+".csr");
}
+99 -31
View File
@@ -6,16 +6,19 @@
// 1. Users.
// Stored in /vitastor/config/user/<username>.
// Has 2 properties:
// - type, one of: osd, mon, admin, client.
// osd, mon types should be used by OSDs/monitors.
// admin should be used for administrative access from vitastor-cli.
// client should be used for regular clients.
// - type, one of: admin, client.
// admin has full access to all images and also to cluster config.
// client has r/w access to owned images and r/o access to images with reader_group.
// - groups, a list of group names the user is included in.
// 2. Images.
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
// - owner (user name)
// - owner_group (group name)
// - reader_group
// 3. Certificates.
// - osd, mon use their own trusted certificates.
const { X509Certificate } = require('node:crypto');
const static_perms = {
invalid: {
@@ -24,7 +27,7 @@ const static_perms = {
},
osd: {
keys: { '/pg/config': false },
prefixes: { '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
},
mon: {
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
@@ -42,15 +45,15 @@ const static_perms = {
},
client: {
keys: { '/config/global': false, '/config/node_placement': false, '/config/pools': false, '/pg/config': false },
prefixes: { '/osd/stats/': false, '/pg/state/': false, '/index/maxid/': false },
prefixes: { '/osd/state/': false, '/pg/state/': false, '/index/maxid/': false },
},
};
const api_perms = {
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true },
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true },
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
admin: { maintenance_status: true },
client: {},
client: { maintenance_status: true },
};
class VitastorAuthFilter
@@ -63,6 +66,43 @@ class VitastorAuthFilter
this.prefix_parts = this.prefix.split('/');
}
async init()
{
if (!this.cfg.cert || !this.cfg.key || !this.cfg.osd_ca || !this.cfg.etcd_proxy && !this.cfg.peer_ca || !this.cfg.client_cert_auth)
{
throw new Error('Authenticated Vitastor setups require enabled client_cert_auth, cert, key'+
' and separate ca (client CA), osd_ca'+(this.cfg.etcd_proxy ? '' : ', peer_ca')+' and optionally mon_ca');
}
this.osd_ca = await this.antietcd.readPEM(this.cfg.osd_ca);
this.osd_ca_obj = new X509Certificate(this.osd_ca);
this.antietcd.tls.ca.push(this.osd_ca);
if (this.cfg.mon_ca)
{
this.mon_ca = await this.antietcd.readPEM(this.cfg.mon_ca);
this.mon_ca_obj = new X509Certificate(this.mon_ca_obj);
this.antietcd.tls.ca.push(this.mon_ca);
}
}
init_context(context, clientCert)
{
let cert = clientCert;
while (cert)
{
if (cert.fingerprint256 == this.osd_ca_obj.fingerprint256)
{
context.user_type = 'osd';
break;
}
if (this.mon_ca_obj && cert.fingerprint256 == this.mon_ca_obj.fingerprint256)
{
context.user_type = 'mon';
break;
}
cert = cert.issuerCertificate;
}
}
_get(path, decode)
{
let cur = this.antietcd.etctree.state;
@@ -263,6 +303,18 @@ class VitastorAuthFilter
}
return false;
}
if (key.substr(0, 13) == '/inode/stats/')
{
const [ pool_id, id ] = key.substr(13).split('/');
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', pool_id, id ], true);
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
return true;
}
return false;
}
}
return false;
}
@@ -349,19 +401,31 @@ class VitastorAuthFilter
return true;
}
_get_user(username)
_get_user(context)
{
if (!username)
if (context.user_type === 'osd' || context.user_type === 'mon')
{
return null;
return {
name: context.user_type,
type: context.user_type,
perms: static_perms[context.user_type],
};
}
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', username ], true);
if (!context.username)
{
return {};
}
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
if (!userInfo)
{
userInfo = { type: 'client' };
}
else if (userInfo.type !== 'client' && userInfo.type !== 'admin')
{
userInfo.type = 'client';
}
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
userInfo.name = username;
userInfo.name = context.username;
if (userInfo.groups instanceof Array)
{
userInfo.groups = userInfo.groups.reduce((a, c) => { a[c] = true; return a; }, {});
@@ -373,23 +437,27 @@ class VitastorAuthFilter
return userInfo;
}
filter_api(username, api/*, data*/)
filter_api(context, api/*, data*/)
{
if (username === 'root')
let type = 'client';
if (context.user_type === 'osd' || context.user_type === 'mon')
{
return true;
type = context.user_type;
}
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', username ], true);
return userInfo && api_perms[userInfo.type] && api_perms[userInfo.type][api];
else if (context.username)
{
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
if (userInfo && userInfo.type === 'admin')
{
type = 'admin';
}
}
return api_perms[type] && api_perms[type][api];
}
filter_txn(username, txn)
filter_txn(context, txn)
{
if (username === 'root')
{
return true;
}
const userInfo = this._get_user(username);
const userInfo = this._get_user(context);
if (!userInfo)
{
return null;
@@ -425,13 +493,13 @@ class VitastorAuthFilter
return txn;
}
filter_txn_response(username, txn, res)
filter_txn_response(context, txn, res)
{
if (!res.responses || username === 'root')
if (!res.responses)
{
return;
}
const userInfo = this._get_user(username);
const userInfo = this._get_user(context);
if (!userInfo)
{
for (const resp of res.responses)
@@ -452,13 +520,13 @@ class VitastorAuthFilter
}
}
filter_watch_message(username, msg)
filter_watch_message(context, msg)
{
if (!msg.result || !msg.result.events || username === 'root')
if (!msg.result || !msg.result.events)
{
return;
}
const userInfo = this._get_user(username);
const userInfo = this._get_user(context);
if (!userInfo)
{
msg.result.events = [];
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor",
"version": "3.0.9",
"version": "3.0.12",
"description": "Low-level native bindings to Vitastor client library",
"main": "index.js",
"keywords": [
+1 -1
View File
@@ -50,7 +50,7 @@ from cinder.volume import configuration
from cinder.volume import driver
from cinder.volume import volume_utils
VITASTOR_VERSION = '3.0.9'
VITASTOR_VERSION = '3.0.12'
LOG = logging.getLogger(__name__)
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.9
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.9.el10.tar.gz
Source0: vitastor-3.0.12.el10.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.9
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.9.el7.tar.gz
Source0: vitastor-3.0.12.el7.tar.gz
BuildRequires: gperftools-devel
BuildRequires: devtoolset-9-gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.9
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.9.el8.tar.gz
Source0: vitastor-3.0.12.el8.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-toolset-9-gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.9
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.9.el9.tar.gz
Source0: vitastor-3.0.12.el9.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
+6 -4
View File
@@ -20,7 +20,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
endif()
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
add_definitions(-DVITASTOR_VERSION="3.0.9")
add_definitions(-DVITASTOR_VERSION="3.0.12")
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
add_link_options(-fno-omit-frame-pointer)
if (${WITH_ASAN})
@@ -69,15 +69,17 @@ pkg_check_modules(ISAL libisal)
if (ISAL_LIBRARIES)
add_definitions(-DWITH_ISAL)
endif (ISAL_LIBRARIES)
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
if (ISAL_CRYPTO_LIBRARIES)
add_definitions(-DWITH_ISAL_CRYPTO)
endif (ISAL_CRYPTO_LIBRARIES)
pkg_check_modules(RDMACM librdmacm)
if (RDMACM_LIBRARIES)
add_definitions(-DWITH_RDMACM)
endif (RDMACM_LIBRARIES)
find_package(OpenSSL REQUIRED)
if (OPENSSL_FOUND)
add_definitions(-DWITH_OPENSSL)
endif (OPENSSL_FOUND)
add_definitions(-DWITH_OPENSSL)
pkg_check_modules(CARES REQUIRED libcares)
include_directories(${CARES_INCLUDE_DIRS})
+1
View File
@@ -100,6 +100,7 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
(config["discard_on_start"] == "true" || config["discard_on_start"] == "1" || config["discard_on_start"] == "yes");
gc_on_start = config.find("gc_on_start") == config.end() ||
(config["gc_on_start"] == "true" || config["gc_on_start"] == "1" || config["gc_on_start"] == "yes");
skip_double_claim = (config["skip_double_claim"] == "true" || config["skip_double_claim"] == "1" || config["skip_double_claim"] == "yes");
min_discard_size = parse_size(config["min_discard_size"]);
if (!min_discard_size)
min_discard_size = 1024*1024;
+2
View File
@@ -60,6 +60,8 @@ struct blockstore_disk_t
bool discard_on_start = false;
// GC on start (new store)
bool gc_on_start = true;
// Skip double claim conflicts on start (new store, temporary until the bug is found)
bool skip_double_claim = false;
uint64_t min_discard_size = 1024*1024;
uint64_t discard_granularity = 0;
+112 -18
View File
@@ -174,14 +174,18 @@ bool journal_flusher_co::loop()
else if (wait_state == 19) goto resume_19;
else if (wait_state == 20) goto resume_20;
else if (wait_state == 21) goto resume_21;
else if (wait_state == 22) goto resume_22;
else if (wait_state == 23) goto resume_23;
else if (wait_state == 24) goto resume_24;
else if (wait_state == 25) goto resume_25;
resume_0:
wait_state = 0;
wait_count = 0;
cur_oid = {};
res = bs->heap->get_next_compact(cur_oid);
// Advance fsynced_lsn every <journal_trim_interval> intent writes
if ((bs->intent_write_counter >= bs->journal_trim_interval) && co_id == 0)
{
// Advance fsynced_lsn every <journal_trim_interval> intent writes
bs->intent_write_counter = 0;
resume_17:
resume_18:
@@ -196,6 +200,7 @@ resume_21:
if (res == ENOENT && flusher->force_start > 0 && co_id == 0 &&
(!bs->dsk.disable_journal_fsync || !bs->dsk.disable_meta_fsync || !bs->dsk.disable_data_fsync))
{
// When under pressure, do an additional fsync to force entries to be marked compactable
flusher->active_flushers++;
resume_14:
resume_15:
@@ -259,11 +264,9 @@ resume_1:
if (wr->type() == BS_HEAP_SMALL_WRITE ||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity)
{
auto res = bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity && !bs->perfect_csum_update
? COPY_BUF_SKIP_CSUM : 0);
if (res > 0)
copy_count++;
}
});
if (!compact_info.compact_lsn)
@@ -273,6 +276,25 @@ resume_1:
bs->heap->unlock_entry(cur_oid);
goto resume_0;
}
flusher->active_flushers++;
for (i = 0; i < read_vec.size(); i++)
{
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
{
copy_count++;
}
}
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
{
init_fsync_data();
}
if (bs->log_level > 10)
{
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
compact_info.clean_wr->version, compact_info.compact_version,
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
}
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
if (!bitmap_copied)
{
@@ -291,13 +313,6 @@ resume_1:
csum_copy.clear();
}
clean_loc = compact_info.clean_wr->big_location(bs->heap);
flusher->active_flushers++;
if (bs->log_level > 10)
{
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
compact_info.clean_wr->version, compact_info.compact_version,
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
}
overwrite_start = overwrite_end = 0;
if (read_vec.size() > 0)
{
@@ -336,6 +351,13 @@ resume_3:
if (res == ENOENT || res == EDOM)
{
// Abort compaction
abort_compact:
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
{
cur_sync->member_count--;
if (cur_sync->member_count > 0)
bs->ringloop->wakeup();
}
flusher->flushing.erase(cur_oid);
bs->heap->unlock_entry(cur_oid);
flusher->active_flushers--;
@@ -349,10 +371,7 @@ resume_4:
if (res == ENOENT)
{
// Abort compaction
flusher->flushing.erase(cur_oid);
bs->heap->unlock_entry(cur_oid);
flusher->active_flushers--;
goto resume_0;
goto abort_compact;
}
if (res == EAGAIN)
{
@@ -381,14 +400,14 @@ resume_9:
for (i = 0; i < read_vec.size(); i++)
{
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
!(read_vec[i].copy_flags & COPY_BUF_COALESCED) ||
(read_vec[i].copy_flags & COPY_BUF_PADDED)) // FIXME Shit, simplify these flags
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
{
assert(read_vec[i].buf);
await_sqe(10);
data->iov = (struct iovec){ read_vec[i].buf + (read_vec[i].copy_flags & COPY_BUF_PADDED
? read_vec[i].offset - read_vec[i].disk_offset : 0), (size_t)read_vec[i].len };
data->callback = simple_callback_w;
assert(clean_loc + read_vec[i].offset + data->iov.iov_len <= bs->dsk.block_count*bs->dsk.data_block_size);
io_uring_prep_writev(sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + read_vec[i].offset);
wait_count++;
}
@@ -399,6 +418,17 @@ resume_11:
wait_state = 11;
return false;
}
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
{
resume_22:
resume_23:
resume_24:
resume_25:
if (!fsync_data(22))
{
return false;
}
}
// Lock is only needed to prevent freeing the big_write because we overwrite it...
bs->heap->unlock_entry(cur_oid);
// Mark the object compacted, but don't free and remove small_writes
@@ -408,12 +438,14 @@ resume_11:
if (!cur_obj)
{
// Abort compaction
flusher->active_flushers--;
flusher->flushing.erase(cur_oid);
goto resume_0;
}
if (!calc_block_checksums())
{
// Abort compaction
flusher->active_flushers--;
flusher->flushing.erase(cur_oid);
goto resume_0;
}
@@ -422,6 +454,7 @@ resume_11:
if (res == EBUSY)
{
// Abort compaction, object is already overwritten by something else
flusher->active_flushers--;
flusher->flushing.erase(cur_oid);
goto resume_0;
}
@@ -586,7 +619,7 @@ int journal_flusher_co::check_and_punch_checksums()
bs->heap->calc_block_checksums((uint32_t*)(new_csums+csum_off), vec.buf, punch_bmp, vec.offset, vec.offset+vec.len, true, NULL);
}
}
// Modified, we should add_punch_holes and then write the block to disk
// Modified, we should punch_holes and then write the block to disk
return EBUSY;
}
@@ -699,6 +732,67 @@ resume_1:
return true;
}
void journal_flusher_co::init_fsync_data()
{
cur_sync = flusher->data_syncs.begin();
if (cur_sync == flusher->data_syncs.end() || cur_sync->ready_count > 0)
{
cur_sync = flusher->data_syncs.emplace(cur_sync);
}
cur_sync->member_count++;
}
bool journal_flusher_co::fsync_data(int wait_base)
{
if (wait_state == wait_base)
goto resume_0;
else if (wait_state == wait_base+1)
goto resume_1;
else if (wait_state == wait_base+2)
goto resume_2;
else if (wait_state == wait_base+3)
goto resume_3;
cur_sync->ready_count++;
resume_0:
if (cur_sync->ready_count < cur_sync->member_count)
{
wait_state = wait_base;
return false;
}
if (!cur_sync->sent)
{
// Sync batch is ready. Do it.
await_sqe(1);
data->iov = { 0 };
data->callback = simple_callback_w;
io_uring_prep_fsync(sqe, bs->dsk.data_fd, IORING_FSYNC_DATASYNC);
cur_sync->sent = true;
wait_count++;
resume_2:
if (wait_count > 0)
{
wait_state = wait_base+2;
return false;
}
cur_sync->done = true;
// Wake up other flushers
bs->ringloop->wakeup();
}
resume_3:
if (!cur_sync->done)
{
wait_state = wait_base+3;
return false;
}
cur_sync->done_count++;
if (cur_sync->done_count >= cur_sync->member_count)
{
flusher->data_syncs.erase(cur_sync);
cur_sync = flusher->data_syncs.end();
}
return true;
}
bool journal_flusher_co::fsync_meta(int wait_base)
{
if (wait_state == wait_base) goto resume_0;
+13
View File
@@ -25,6 +25,15 @@ struct flusher_meta_write_t
std::map<uint64_t, meta_sector_t>::iterator it;
};
struct flusher_data_sync_t
{
int member_count = 0;
int ready_count = 0;
int done_count = 0;
bool sent = false;
bool done = false;
};
class journal_flusher_t;
// Journal flusher coroutine
@@ -58,6 +67,7 @@ class journal_flusher_co
int i, res;
bool read_to_fill_incomplete;
int copy_count;
std::list<flusher_data_sync_t>::iterator cur_sync;
friend class journal_flusher_t;
@@ -68,6 +78,8 @@ class journal_flusher_co
bool calc_block_checksums();
bool write_meta_block(int wait_base);
bool read_buffered(int wait_base);
void init_fsync_data();
bool fsync_data(int wait_base);
bool fsync_meta(int wait_base);
bool fsync_buffer(int wait_base);
bool trim_lsn(int wait_base);
@@ -88,6 +100,7 @@ class journal_flusher_t
robin_hood::unordered_flat_set<object_id> flushing;
int active_flushers = 0;
std::list<flusher_data_sync_t> data_syncs;
int wanting_meta_fsync = 0;
bool fsyncing_meta = false;
int syncing_buffer = 0;
+333 -178
View File
@@ -29,6 +29,7 @@
#define IMAP_MALLOC_LOW_BITS ((size_t)0x0F)
#define IMAP_MAX_LOW 16
#define POSTPONE_INSERT_COUNT 10
#define list_item_overhead(a) (((a) + sizeof(heap_list_item_t) - sizeof(heap_entry_t) + sizeof(void*) + 15) & ~15)
@@ -127,26 +128,26 @@ uint32_t heap_entry_t::get_size(blockstore_heap_t *heap)
return heap->get_simple_entry_size();
}
bool heap_entry_t::is_overwrite()
bool heap_entry_t::is_overwrite() const
{
return ((entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_BIG_WRITE|BS_HEAP_STABLE) ||
(entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_BIG_INTENT|BS_HEAP_STABLE) ||
(entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
}
bool heap_entry_t::is_compactable()
bool heap_entry_t::is_compactable() const
{
return !is_overwrite() && (entry_type & BS_HEAP_STABLE) ||
(entry_type & ~BS_HEAP_GARBAGE) == BS_HEAP_COMMIT ||
(entry_type & ~BS_HEAP_GARBAGE) == BS_HEAP_ROLLBACK;
}
bool heap_entry_t::is_before(heap_entry_t *other)
bool heap_entry_t::is_before(const heap_entry_t *other) const
{
return lsn < other->lsn || lsn == other->lsn && !is_overwrite() && other->is_overwrite();
}
bool heap_entry_t::is_garbage()
bool heap_entry_t::is_garbage() const
{
return (entry_type & BS_HEAP_GARBAGE);
}
@@ -418,6 +419,13 @@ corrupted_object:
wr->inode, wr->stripe, wr->version, wr->big_intent().offset, wr->big_intent().len);
goto corrupted_object;
}
if ((wr->type() == BS_HEAP_BIG_INTENT || wr->type() == BS_HEAP_BIG_WRITE) &&
wr->big().block_num >= dsk->block_count)
{
fprintf(stderr, "Error: big_write or big_intent entry %jx:%jx v%ju block_num is too large: %u > %lu. Metadata is incompatible with current parameters. ",
wr->inode, wr->stripe, wr->version, wr->big_intent().block_num, dsk->block_count);
goto corrupted_object;
}
handle_write(block_num, wr);
block_offset += wr->size;
}
@@ -444,7 +452,7 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
next_lsn = wr->lsn;
}
entries_loaded++;
loaded_list_items.push_back(li);
insert_list_items(&li, 1, true);
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
if (!inf.entries.size())
@@ -550,18 +558,26 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
void blockstore_heap_t::finish_load()
{
if (loaded_list_items.size())
if (postponed_items.size())
{
// Sort everything and load in correct order
std::sort(loaded_list_items.begin(), loaded_list_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
// Sort "postponed" items and load in batches
std::sort(postponed_items.begin(), postponed_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
{
return a->entry.lsn < b->entry.lsn;
return a->entry.inode < b->entry.inode || a->entry.inode == b->entry.inode &&
(a->entry.stripe < b->entry.stripe || a->entry.stripe == b->entry.stripe &&
!a->entry.is_before(&b->entry)); // object ASC, lsn DESC
});
for (auto & li: loaded_list_items)
size_t s = 0, e, n = postponed_items.size();
for (e = 1; e <= n; e++)
{
insert_list_item(li);
if (e >= n || postponed_items[e]->entry.inode != postponed_items[s]->entry.inode &&
postponed_items[e]->entry.stripe != postponed_items[s]->entry.stripe)
{
insert_list_items(postponed_items.data()+s, e-s, false);
s = e;
}
}
loaded_list_items.clear();
postponed_items.clear();
}
}
@@ -574,26 +590,11 @@ void blockstore_heap_t::fill_recheck_queue()
inode_map_iterate(ip.second, [&](heap_list_item_t *li)
{
auto obj = &li->entry;
// Add object to recheck queue
if (obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
// Recheck only the latest intent_write (if after completed_lsn) or a series of small_writes
if ((obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
&& obj->lsn > completed_lsn || obj->type() == BS_HEAP_SMALL_WRITE)
{
// Recheck only the latest intent_write
if (obj->lsn > completed_lsn)
{
// Do not recheck if it's already marked as completed in the superblock
recheck_queue.push_back(obj);
}
}
else
{
// Or recheck a series of small_writes
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
{
if (wr->small().len > 0)
{
recheck_queue.push_back(wr);
}
}
recheck_queue.push_back(obj);
}
});
}
@@ -603,6 +604,11 @@ void blockstore_heap_t::fill_recheck_queue()
int blockstore_heap_t::mark_used_blocks()
{
int res = 0;
std::vector<heap_list_item_t*> used_by;
if (dsk->skip_double_claim)
{
used_by.resize(dsk->block_count);
}
for (auto & pgp: block_index)
{
for (auto & ip: pgp.second)
@@ -656,10 +662,34 @@ int blockstore_heap_t::mark_used_blocks()
{
if (is_data_used(wr->big_location(this)))
{
fprintf(stderr, "Error: double-claimed data block %u, second time by %jx:%jx l%ju\n",
wr->big().block_num, wr->inode, wr->stripe, wr->lsn);
res = EDOM;
return;
if (dsk->skip_double_claim)
{
// There is a BUG currently:
// Sometimes (under unknown conditions) deletion entries are removed from the disk
// earlier than previous big_writes.
// Until it's fixed, we provide a way to ignore such objects on start.
auto prev_li = used_by[wr->big().block_num];
assert(prev_li);
// Newer LSN must be trusted. Remove the older object.
fprintf(stderr, "Block %u is double-claimed by entries %jx:%jx l%ju and %jx:%jx l%ju\n",
wr->big().block_num, prev_li->entry.inode, prev_li->entry.stripe, prev_li->entry.lsn, wr->inode, wr->stripe, wr->lsn);
if (init_erase_double_claim(prev_li, li))
{
return;
}
}
else
{
fprintf(stderr, "Error: double-claimed data block %u, second time by %jx:%jx l%ju\n",
wr->big().block_num, wr->inode, wr->stripe, wr->lsn);
res = EDOM;
return;
}
}
if (dsk->skip_double_claim)
{
// Record the object which uses the data block
used_by[wr->big().block_num] = li;
}
use_data(wr->inode, wr->big_location(this));
}
@@ -683,6 +713,116 @@ int blockstore_heap_t::mark_used_blocks()
return res;
}
void blockstore_heap_t::init_free_bad_entry(heap_entry_t *wr)
{
if (wr->type() == BS_HEAP_SMALL_WRITE)
{
free_buffer_area(wr->inode, wr->small().location, wr->small().len);
}
else if (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT)
{
free_data(wr->inode, wr->big_location(this));
}
}
void blockstore_heap_t::init_erase_bad_entry(heap_list_item_t *li)
{
modify_alloc(li->block_num, [&](heap_block_info_t & inf)
{
for (size_t i = 0; i < inf.entries.size(); i++)
{
if (inf.entries[i] == li)
{
inf.entries.erase(inf.entries.begin()+i);
break;
}
}
inf.used_space -= li->entry.size;
inf.garbage_space -= (li->entry.is_garbage() ? li->entry.size : 0);
});
recheck_modified_blocks.insert(li->block_num);
unlink_list_item(li);
}
bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li)
{
bool erase_prev = false;
bool erase_cur = false;
if (prev_li->entry.lsn < cur_li->entry.lsn)
{
erase_prev = true;
auto latest_li = prev_li;
while (latest_li->next)
{
latest_li = latest_li->next;
}
if (latest_li->entry.lsn >= cur_li->entry.lsn)
{
// LSN ranges intersect, erase both
erase_cur = true;
}
}
else
{
erase_cur = true;
auto latest_li = cur_li;
while (latest_li->next)
{
latest_li = latest_li->next;
}
if ((latest_li->entry.inode != prev_li->entry.inode ||
latest_li->entry.stripe != prev_li->entry.stripe) &&
latest_li->entry.lsn >= prev_li->entry.lsn)
{
// LSN ranges intersect, erase both
erase_prev = true;
}
}
if (erase_prev)
{
fprintf(stderr, "Erasing object %jx:%jx due to double-claim\n", prev_li->entry.inode, prev_li->entry.stripe);
auto erase_li = prev_li;
while (erase_li->next)
{
erase_li = erase_li->next;
}
bool overwritten = false;
while (erase_li)
{
auto prev_erase_li = erase_li->prev;
if (!overwritten)
{
init_free_bad_entry(&erase_li->entry);
overwritten = erase_li->entry.is_overwrite();
}
init_erase_bad_entry(erase_li);
erase_li = prev_erase_li;
}
}
if (erase_cur)
{
fprintf(stderr, "Erasing object %jx:%jx due to double-claim\n", cur_li->entry.inode, cur_li->entry.stripe);
auto erase_li = cur_li->next;
while (erase_li)
{
// Only newer entries are marked as used
auto next_erase_li = erase_li->next;
init_free_bad_entry(&erase_li->entry);
init_erase_bad_entry(erase_li);
erase_li = next_erase_li;
}
erase_li = cur_li;
// Older ones are not
while (erase_li)
{
auto prev_erase_li = erase_li->prev;
init_erase_bad_entry(erase_li);
erase_li = prev_erase_li;
}
}
return erase_cur;
}
void blockstore_heap_t::recheck_full_gc()
{
uint32_t block_num = 0;
@@ -723,80 +863,98 @@ void blockstore_heap_t::recheck_full_gc()
}
}
void blockstore_heap_t::recheck_buffer(heap_entry_t *cwr, uint8_t *buf)
void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr)
{
auto free_entry = [&](heap_list_item_t *li)
// write entry is invalid, erase it and all newer entries
int bad_count = 1;
for (auto wr = obj; wr && wr != bad_wr; wr = prev(wr))
{
uint32_t block_num = li->block_num;
auto wr_size = li->entry.size;
if (li->entry.is_garbage())
{
garbage_entries--;
garbage_memory -= list_item_overhead(wr_size);
}
live_entries--;
live_memory -= list_item_overhead(wr_size);
free(li);
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
inf.used_space -= wr_size;
bool found = false;
for (auto it = inf.entries.begin(); it != inf.entries.end(); it++)
{
if (*it == li)
{
found = true;
inf.entries.erase(it);
break;
}
}
assert(found);
});
recheck_modified_blocks.insert(block_num);
};
if (cwr->is_garbage())
{
// already freed after rechecking one of the previous small_write entries
free_entry(list_item(cwr));
bad_count++;
}
else if (!calc_checksums(cwr, buf, false))
auto prev_wr = prev(bad_wr);
if (prev_wr)
{
// write entry is invalid, erase it and mark newer entries with garbage bit
auto & pg_idx = block_index[get_pg_id(cwr->inode, cwr->stripe)];
auto & inode_idx = pg_idx[cwr->inode];
heap_inode_map_t::iterator li_it;
heap_list_item_t *li = NULL;
inode_map_get(inode_idx, li_it, li, cwr->stripe);
int rolled_back = 1;
while (li && cwr != &li->entry)
fprintf(stderr, "Notice: %u unfinished %s to %jx:%jx v%ju since good lsn %ju, rolling back\n",
bad_count, bad_count > 1 ? "writes" : "write", obj->inode, obj->stripe, obj->version, prev_wr->lsn);
}
else
{
fprintf(stderr, "Notice: the whole object %jx:%jx only has unfinished writes, rolling back\n", obj->inode, obj->stripe);
}
auto li = list_item(obj);
while (li && prev_wr != &li->entry)
{
auto prev = li->prev;
assert(li->entry.type() == bad_wr->type());
init_erase_bad_entry(li);
li = prev;
}
}
void blockstore_heap_t::recheck_start_reads(heap_recheck_state_t *st)
{
if (st->sent_reads >= st->total_reads)
return;
while (recheck_in_progress < recheck_queue_depth)
{
auto wr = st->next_wr;
st->next_wr = prev(st->next_wr);
uint64_t loc = 0, len = 0;
bool from_data = false;
if (wr->type() == BS_HEAP_SMALL_WRITE)
{
assert(li->entry.entry_type == cwr->entry_type);
auto prev = li->prev;
li->next = li->prev = NULL;
if (!li->entry.is_garbage())
{
garbage_entries++;
garbage_memory += list_item_overhead(li->entry.size);
li->entry.set_garbage();
}
li = prev;
rolled_back++;
loc = wr->small().location;
len = wr->small().len;
}
assert(li);
if (li->prev)
else if (wr->type() == BS_HEAP_BIG_INTENT)
{
fprintf(stderr, "Notice: %u unfinished %s to %jx:%jx v%ju since lsn %ju, rolling back\n",
rolled_back, rolled_back > 1 ? "writes" : "write", cwr->inode, cwr->stripe, li->prev->entry.version, li->entry.lsn);
inode_map_replace(inode_idx, li_it, li->prev);
li->prev->next = NULL;
auto & bi = wr->big_intent();
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
len = bi.len;
from_data = true;
}
else
{
fprintf(stderr, "Notice: the whole object %jx:%jx only has unfinished writes, rolling back\n",
cwr->inode, cwr->stripe);
inode_map_erase(pg_idx, inode_idx, li_it, li);
assert(wr->type() == BS_HEAP_INTENT_WRITE);
auto prev_wr = prev(wr);
while (prev_wr && prev_wr->entry_type == wr->entry_type)
{
// Skip other intent_writes
prev_wr = prev(prev_wr);
}
if (!prev_wr || prev_wr->entry_type != (BS_HEAP_BIG_WRITE | (wr->entry_type & BS_HEAP_STABLE)) &&
prev_wr->entry_type != (BS_HEAP_BIG_INTENT | (wr->entry_type & BS_HEAP_STABLE)))
{
fprintf(stderr, "Error: intent_write entry %jx:%jx v%ju l%ju is not written over a big_write\n",
wr->inode, wr->stripe, wr->version, wr->lsn);
exit(1);
}
loc = wr->small().offset + prev_wr->big_location(this);
len = wr->small().len;
from_data = true;
}
free_entry(li);
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
st->sent_reads++;
recheck_in_progress++;
recheck_pending_reads--;
bool is_last = st->sent_reads >= st->total_reads;
recheck_cb(from_data, loc, len, buf, [this, st, wr, buf]()
{
st->checked_reads++;
if (!calc_checksums(wr, buf, false))
st->bad_wr = !st->bad_wr || st->bad_wr->lsn > wr->lsn ? wr : st->bad_wr;
if (st->checked_reads >= st->total_reads)
{
if (st->bad_wr)
recheck_drop_entries(st->obj, st->bad_wr);
recheck_states.erase(st->obj);
}
free(buf);
recheck_in_progress--;
recheck_small_writes(NULL, 0);
});
if (is_last)
break;
}
}
@@ -819,70 +977,47 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
recheck_queue_depth = queue_depth;
}
in_recheck = true;
while (recheck_pending_reads > 0 && recheck_in_progress < recheck_queue_depth)
{
for (auto & sp: recheck_states)
recheck_start_reads(&sp.second);
}
while (recheck_queue.size() > 0 && recheck_in_progress < recheck_queue_depth)
{
heap_entry_t *wr = recheck_queue.front();
heap_entry_t *obj = recheck_queue.front();
recheck_queue.pop_front();
bool from_data = false;
uint64_t loc = 0;
uint32_t len = 0;
if (wr->type() == BS_HEAP_INTENT_WRITE)
if (obj->type() == BS_HEAP_SMALL_WRITE && buffer_area)
{
auto prev_wr = prev(wr);
while (prev_wr && prev_wr->entry_type == wr->entry_type)
// Check this object synchronously
heap_entry_t *bad_wr = NULL;
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
{
// Skip other intent_writes
prev_wr = prev(prev_wr);
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in buffer area\n",
wr->inode, wr->stripe, wr->lsn, wr->small().len, wr->small().location);
if (!calc_checksums(wr, buffer_area + wr->small().location, false))
bad_wr = wr;
}
if (!prev_wr || prev_wr->entry_type != (BS_HEAP_BIG_WRITE | (wr->entry_type & BS_HEAP_STABLE)) &&
prev_wr->entry_type != (BS_HEAP_BIG_INTENT | (wr->entry_type & BS_HEAP_STABLE)))
{
fprintf(stderr, "Error: intent_write entry %jx:%jx v%ju l%ju is not written over a big_write\n",
wr->inode, wr->stripe, wr->version, wr->lsn);
exit(1);
}
loc = wr->small().offset + prev_wr->big_location(this);
len = wr->small().len;
from_data = true;
}
else if (wr->type() == BS_HEAP_BIG_INTENT)
{
auto & bi = wr->big_intent();
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
len = bi.len;
from_data = true;
if (bad_wr)
recheck_drop_entries(obj, bad_wr);
}
else
{
assert(wr->type() == BS_HEAP_SMALL_WRITE);
loc = wr->small().location;
len = wr->small().len;
}
if (log_level > 5)
{
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in %s area\n",
wr->inode, wr->stripe, wr->lsn, len, loc, from_data ? "data" : "buffer");
}
if (!from_data && buffer_area)
{
recheck_buffer(wr, buffer_area+loc);
}
else
{
recheck_in_progress++;
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
recheck_cb(from_data, loc, len, buf, [this, wr, buf]()
{
recheck_buffer(wr, buf);
free(buf);
recheck_in_progress--;
recheck_small_writes(NULL, 0);
});
// Recheck will be asynchronous. Create state and start it
auto & st = recheck_states[obj];
st.obj = obj;
st.next_wr = obj;
st.total_reads = 1;
if (obj->type() == BS_HEAP_SMALL_WRITE)
for (auto wr = prev(obj); wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
st.total_reads++;
recheck_pending_reads += st.total_reads;
recheck_start_reads(&st);
}
}
in_recheck = false;
if (!recheck_queue.size() && !recheck_in_progress)
{
assert(!recheck_states.size());
auto cb = std::move(recheck_cb);
recheck_queue_depth = 0;
if (cb)
@@ -1404,43 +1539,51 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
return 0;
}
void blockstore_heap_t::insert_list_item(heap_list_item_t *li)
void blockstore_heap_t::insert_list_items(heap_list_item_t** v, size_t count, bool postpone)
{
auto & inode_idx = block_index[get_pg_id(li->entry.inode, li->entry.stripe)][li->entry.inode];
auto wr = &v[0]->entry;
auto & inode_idx = block_index[get_pg_id(wr->inode, wr->stripe)][wr->inode];
heap_inode_map_t::iterator li_it;
heap_list_item_t *old_head = NULL;
if (inode_idx)
inode_map_get(inode_idx, li_it, old_head, li->entry.stripe);
if (old_head && !old_head->entry.is_before(&li->entry))
inode_map_get(inode_idx, li_it, old_head, wr->stripe);
heap_list_item_t *next_li = NULL;
heap_list_item_t *prev_li = old_head;
int skips = 0;
// Merge entry array and inode_idx linked list (both sorted in newest first order)
for (size_t i = 0; i < count; i++)
{
// BIG_WRITE may be inserted into the middle of the sequence during compaction
// and it overrides SMALL_WRITEs and COMMITs with the same LSN
// However, all entries of other types (say DELETE) override previous ones
auto next_li = old_head;
auto prev_li = old_head->prev;
auto li = v[i];
while (prev_li && !prev_li->entry.is_before(&li->entry))
{
next_li = prev_li;
prev_li = prev_li->prev;
skips++;
}
if (postpone && skips > POSTPONE_INSERT_COUNT)
{
postponed_items.push_back(li);
return;
}
if (next_li == NULL)
{
// Replace the latest entry pointer
if (old_head)
inode_map_replace(inode_idx, li_it, li);
else
inode_map_put(inode_idx, li);
}
// Insert <li> between <next_li> and <prev_li>
li->next = next_li;
if (next_li)
next_li->prev = li;
li->prev = prev_li;
if (prev_li)
prev_li->next = li;
next_li->prev = li;
li->next = next_li;
}
else
{
li->prev = old_head;
li->next = NULL;
if (old_head)
{
old_head->next = li;
inode_map_replace(inode_idx, li_it, li);
}
else
inode_map_put(inode_idx, li);
next_li = li;
}
}
@@ -1472,7 +1615,7 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
(explicit_complete ? HEAP_INFLIGHT_EXPLICIT : 0) |
(new_wr->is_overwrite() ? HEAP_INFLIGHT_COMPACTED : 0) |
(new_wr->is_compactable() ? HEAP_INFLIGHT_COMPACTABLE : 0));
insert_list_item(li);
insert_list_items(&li, 1, false);
li->block_num = block_num;
new_wr->size = wr_size;
new_wr->checksum = new_wr->calc_checksum(this);
@@ -1668,6 +1811,7 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
*modified_block = block_num;
memcpy(wr->get_int_bitmap(this), new_bitmap, dsk->clean_entry_bitmap_size);
memcpy(wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
wr->checksum = wr->calc_checksum(dsk);
return 0;
}
@@ -2466,6 +2610,22 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
}
void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
{
if (!li->next)
{
// The last freed entry must be a deletion
assert(!li->prev);
assert((li->entry.entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
}
else if (!li->prev && li->next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
{
// free BS_HEAP_DELETEs when all previous entries are also freed
mark_garbage(li->next->block_num, &li->next->entry, UINT32_MAX);
}
unlink_list_item(li);
}
void blockstore_heap_t::unlink_list_item(heap_list_item_t *li)
{
auto prev = li->prev;
auto next = li->next;
@@ -2475,25 +2635,20 @@ void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
}
if (!next)
{
// The last freed entry must be a deletion
assert(!prev);
auto wr = &li->entry;
assert(wr->entry_type == BS_HEAP_DELETE|BS_HEAP_STABLE);
auto & pg_idx = block_index[get_pg_id(wr->inode, wr->stripe)];
auto & inode_idx = pg_idx[wr->inode];
heap_inode_map_t::iterator li_it;
heap_list_item_t *old_li = NULL;
inode_map_get(inode_idx, li_it, old_li, wr->stripe);
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
if (!prev)
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
else
inode_map_replace(inode_idx, li_it, prev);
}
else
{
next->prev = prev;
if (!prev && next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
{
// free BS_HEAP_DELETEs when all previous entries are also freed
mark_garbage(next->block_num, &next->entry, UINT32_MAX);
}
}
if (li->entry.is_garbage())
{
+24 -6
View File
@@ -57,11 +57,11 @@ struct __attribute__((__packed__)) heap_entry_t
inline heap_small_write_t& small() { return *(heap_small_write_t*)this; }
inline heap_big_write_t& big() { return *(heap_big_write_t*)this; }
inline heap_big_intent_t& big_intent() { return *(heap_big_intent_t*)this; }
bool is_garbage();
bool is_garbage() const;
void set_garbage();
bool is_overwrite();
bool is_compactable();
bool is_before(heap_entry_t *other);
bool is_overwrite() const;
bool is_compactable() const;
bool is_before(const heap_entry_t *other) const;
uint32_t get_size(blockstore_heap_t *heap);
uint8_t *get_ext_bitmap(blockstore_heap_t *heap);
uint8_t *get_int_bitmap(blockstore_heap_t *heap);
@@ -159,6 +159,16 @@ struct heap_li_equal
}
};
struct heap_recheck_state_t
{
heap_entry_t *obj = NULL;
heap_entry_t *next_wr = NULL;
size_t total_reads = 0;
size_t sent_reads = 0;
size_t checked_reads = 0;
heap_entry_t *bad_wr = NULL;
};
using i64hash_t = robin_hood::hash<uint64_t>;
using heap_inode_map_t = robin_hood::unordered_flat_set<heap_list_item_t*, heap_li_hash, heap_li_equal, 88>;
using heap_block_index_t = robin_hood::unordered_flat_map<uint64_t,
@@ -209,9 +219,11 @@ class blockstore_heap_t
bool marked_used_blocks = false;
bool recheck_queue_filled = false;
std::vector<heap_list_item_t*> loaded_list_items;
std::vector<heap_list_item_t*> postponed_items;
std::set<uint32_t> recheck_modified_blocks;
std::deque<heap_entry_t*> recheck_queue;
std::map<heap_entry_t*, heap_recheck_state_t> recheck_states;
size_t recheck_pending_reads = 0;
int recheck_in_progress = 0;
bool in_recheck = false;
std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> recheck_cb;
@@ -220,7 +232,12 @@ class blockstore_heap_t
uint64_t get_pg_id(inode_t inode, uint64_t stripe);
bool validate_object(heap_entry_t *obj);
void fill_recheck_queue();
void recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr);
void recheck_start_reads(heap_recheck_state_t *st);
int mark_used_blocks();
void init_free_bad_entry(heap_entry_t *wr);
void init_erase_bad_entry(heap_list_item_t *li);
bool init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li);
void recheck_full_gc();
void recheck_buffer(heap_entry_t *cwr, uint8_t *buf);
void defragment_block(uint32_t block_num);
@@ -228,8 +245,9 @@ class blockstore_heap_t
void gc_block(heap_block_info_t & inf);
int allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free);
void insert_list_item(heap_list_item_t *li);
void insert_list_items(heap_list_item_t** v, size_t count, bool postpone);
void remove_list_item(heap_list_item_t *li);
void unlink_list_item(heap_list_item_t *li);
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry);
int add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type);
+8 -3
View File
@@ -101,6 +101,7 @@ void blockstore_impl_t::loop()
unsigned initial_ring_space = ringloop->space_left();
int op_idx = 0, new_idx = 0;
bool has_unfinished_writes = false;
bool has_unfinished_sync = false;
for (; op_idx < submit_queue.size(); op_idx++, new_idx++)
{
auto op = submit_queue[op_idx];
@@ -138,7 +139,13 @@ void blockstore_impl_t::loop()
else if (op->opcode == BS_OP_SYNC)
{
// syncs only completed writes, so doesn't have to be blocked by anything
wr_st = continue_sync(op);
if (!has_unfinished_sync)
{
wr_st = continue_sync(op);
has_unfinished_sync = (wr_st != 2);
}
else
wr_st = 0;
}
else if (op->opcode == BS_OP_STABLE || op->opcode == BS_OP_ROLLBACK)
{
@@ -154,9 +161,7 @@ void blockstore_impl_t::loop()
wr_st = 2;
}
else
{
wr_st = 0;
}
}
if (wr_st == 2)
{
+24 -16
View File
@@ -153,6 +153,14 @@ resume_1:
);
exit(1);
}
uint32_t csum = hdr->header_csum;
hdr->header_csum = 0;
if (crc32c(0, hdr, sizeof(*hdr)) != csum)
{
printf("Metadata header is corrupt (checksum mismatch).\n");
exit(1);
}
hdr->header_csum = csum;
}
bs->heap->start_load(((blockstore_meta_header_v3_t *)bs->meta_superblock)->completed_lsn);
if (bs->dsk.inmemory_journal)
@@ -241,22 +249,6 @@ resume_4:
// metadata read finished
bs->heap->finish_load();
printf("Metadata entries loaded: %ju, rechecking unfinished writes and garbage entries\n", entries_loaded);
if (zero_on_init && !bs->dsk.disable_meta_fsync)
{
GET_SQE();
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
last_read_offset = 0;
data->iov = { 0 };
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
submitted++;
bs->ringloop->submit();
resume_5:
if (submitted > 0)
{
wait_state = 5;
return 1;
}
}
// asynchronous recheck
resume_6:
wait_state = 6;
@@ -337,6 +329,22 @@ resume_9:
}
free(metadata_buffer);
metadata_buffer = NULL;
if (!bs->dsk.disable_meta_fsync && !bs->readonly)
{
GET_SQE();
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
last_read_offset = 0;
data->iov = { 0 };
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
submitted++;
bs->ringloop->submit();
resume_5:
if (submitted > 0)
{
wait_state = 5;
return 1;
}
}
printf("Loading finished. Data used: %ju / %ju bytes (%s / %s)\n",
bs->heap->get_data_used_space(), bs->dsk.block_count * bs->dsk.data_block_size,
format_size(bs->heap->get_data_used_space()).c_str(),
+7
View File
@@ -16,6 +16,7 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
else if (priv->op_state == 5) goto resume_5;
assert(!priv->op_state);
op->retval = 0;
PRIV(op)->lsn = 0;
priv->modified_block = priv->modified_block2 = UINT32_MAX;
for (priv->stab_pos = 0; priv->stab_pos < op->len; priv->stab_pos++)
{
@@ -36,6 +37,12 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
FINISH_OP(op);
return 2;
}
if (res == ENOENT)
{
op->retval = -ENOENT;
FINISH_OP(op);
return 2;
}
if (res == ENOSPC)
{
if (!heap->get_to_compact_count())
+4 -2
View File
@@ -9,6 +9,7 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
if (!PRIV(op)->op_state)
{
op->retval = 0;
PRIV(op)->lsn = 0;
}
int res = do_sync(op, 0);
if (res == 2)
@@ -104,7 +105,8 @@ int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
unsynced_data_write_count = unsynced_buffer_write_count = unsynced_meta_write_count = 0;
return 2;
}
PRIV(op)->modified_block = heap->get_completed_lsn();
assert(!PRIV(op)->lsn);
PRIV(op)->lsn = heap->get_completed_lsn();
if (!submit_fsyncs(PRIV(op)->pending_ops))
{
PRIV(op)->wait_detail = 1;
@@ -118,6 +120,6 @@ resume_1:
return 1;
}
resume_2:
heap->mark_lsn_fsynced(PRIV(op)->modified_block);
heap->mark_lsn_fsynced(PRIV(op)->lsn);
return 2;
}
+4
View File
@@ -37,6 +37,7 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
heap->complete_block_write(modified_block);
ringloop->wakeup();
};
assert(((uint64_t)modified_block+2)*dsk.meta_block_size <= dsk.meta_area_size);
io_uring_prep_writev(
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
);
@@ -177,6 +178,7 @@ enospc:
ring_data_t *data = ((ring_data_t*)sqe->user_data);
data->iov = (struct iovec){ op->buf, op->len };
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
assert(loc+op->offset+op->len <= dsk.block_count*dsk.data_block_size);
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
PRIV(op)->pending_ops++;
write_iodepth++;
@@ -264,6 +266,7 @@ enospc:
BS_SUBMIT_GET_SQE(sqe2, data2);
data2->iov = (struct iovec){ op->buf, op->len };
data2->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
assert(loc+op->len <= dsk.journal_len);
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, dsk.journal_offset + loc);
PRIV(op)->pending_ops++;
}
@@ -453,6 +456,7 @@ resume_10:
BS_SUBMIT_GET_SQE(sqe, data);
data->iov = (struct iovec){ op->buf, op->len };
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
assert(PRIV(op)->location + op->offset <= dsk.block_count*dsk.data_block_size);
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + PRIV(op)->location + op->offset);
if (dsk.use_atomic_flag)
sqe->rw_flags = RWF_ATOMIC;
+1 -1
View File
@@ -141,7 +141,7 @@ struct __attribute__((__packed__)) journal_entry
inline uint32_t je_crc32(journal_entry *je)
{
// 0x48674bc7 = crc32(4 zero bytes)
return crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
return je->size < 4 ? 0 : crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
}
// "VITAstor"
+2
View File
@@ -520,6 +520,7 @@ resume_2:
await_sqe(15);
data->iov = (struct iovec){ it->buf, (size_t)it->len };
data->callback = simple_callback_w;
assert(clean_loc+it->offset+it->len <= bs->dsk.block_count*bs->dsk.data_block_size);
io_uring_prep_writev(
sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + it->offset
);
@@ -749,6 +750,7 @@ bool journal_flusher_co::write_meta_block(flusher_meta_write_t & meta_block, int
await_sqe(0);
data->iov = (struct iovec){ meta_block.buf, (size_t)bs->dsk.meta_block_size };
data->callback = simple_callback_w;
assert(bs->dsk.meta_block_size + meta_block.sector + bs->dsk.meta_block_size <= bs->dsk.meta_area_size);
io_uring_prep_writev(
sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bs->dsk.meta_block_size + meta_block.sector
);
+1
View File
@@ -193,6 +193,7 @@ void blockstore_impl_t::prepare_journal_sector_write(int cur_sector, blockstore_
(size_t)journal.block_size
};
data->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
assert(journal.sector_info[cur_sector].offset+journal.block_size <= dsk.journal_len);
io_uring_prep_writev(
sqe, dsk.journal_fd, &data->iov, 1, journal.offset + journal.sector_info[cur_sector].offset
);
+5 -6
View File
@@ -368,9 +368,9 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
}
data->iov.iov_len = op->len + stripe_offset + stripe_end; // to check it in the callback
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
io_uring_prep_writev(
sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + (loc * dsk.data_block_size) + op->offset - stripe_offset
);
const uint64_t write_offset = (loc * dsk.data_block_size) + op->offset - stripe_offset;
assert(write_offset+op->len+stripe_offset+stripe_end <= dsk.block_count*dsk.data_block_size);
io_uring_prep_writev(sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + write_offset);
PRIV(op)->pending_ops = 1;
if (!(dirty_it->second.state & BS_ST_INSTANT))
{
@@ -495,9 +495,8 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
.op = op,
});
data2->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
io_uring_prep_writev(
sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free
);
assert(journal.next_free+op->len <= dsk.journal_len);
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free);
PRIV(op)->pending_ops++;
}
else
+6 -8
View File
@@ -12,11 +12,11 @@ if (RDMACM_LIBRARIES)
set(MSGR_RDMACM "msgr_rdmacm.cpp")
endif (RDMACM_LIBRARIES)
add_library(vitastor_common STATIC
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp ../util/xxh_x86dispatch.c
msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp msgr_iothread.cpp ../util/addr_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp
msgr_encrypt.cpp msgr_handshake.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
)
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES})
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
target_compile_options(vitastor_common PUBLIC -fPIC)
# libvitastor_client.so
@@ -35,6 +35,7 @@ target_link_libraries(vitastor_client
${IBVERBS_LIBRARIES}
${RDMACM_LIBRARIES}
${OPENSSL_LIBRARIES}
${ISAL_CRYPTO_LIBRARIES}
)
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
configure_file(vitastor.pc.in vitastor.pc @ONLY)
@@ -54,9 +55,6 @@ if (${WITH_FIO})
../util/rw_blocking.cpp
../util/addr_util.cpp
)
target_link_libraries(fio_vitastor_sec
tcmalloc_minimal
)
endif (${WITH_FIO})
# vitastor-nbd
@@ -101,9 +99,9 @@ add_executable(test_cluster_client
EXCLUDE_FROM_ALL
../test/test_cluster_client.cpp
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp cluster_client_icache.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../util/xxh_x86dispatch.c ../../json11/json11.cpp
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp ../../json11/json11.cpp
)
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES})
target_link_libraries(test_cluster_client ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
add_dependencies(build_tests test_cluster_client)
+15 -17
View File
@@ -51,7 +51,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
msgr.stop_client(op->client_id);
delete op;
};
msgr.parse_config(config);
msgr.parse_config(config, true);
st_cli.tfd = tfd;
st_cli.on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
@@ -481,7 +481,7 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
}
// vault
vault_parse_config();
msgr.parse_config(config);
msgr.parse_config(config, false);
st_cli.parse_config(config);
st_cli.load_pgs();
}
@@ -1162,6 +1162,13 @@ resume_2:
// Finished successfully
// Even if the PG count has changed in meanwhile we treat it as success
// because if some operations were invalid for the new PG count we'd get errors
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
{
// Copy part bitmaps only after finishing all part reads
for (auto & part: op->parts)
if ((part.flags & (PART_SENT|PART_DONE|PART_VALID)) == (PART_SENT|PART_DONE|PART_VALID))
copy_part_bitmap(op, &part);
}
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
{
uint64_t next_inode = 0;
@@ -1209,7 +1216,7 @@ resume_2:
erase_op(op);
return 1;
}
else if (op->retval != 0 && !(op->flags & OP_FLUSH_BUFFER) &&
else if (op->retval != 0 && op->opcode != OSD_OP_SYNC && !(op->flags & OP_FLUSH_BUFFER) &&
op->retval != -EPIPE && (op->retval != -EIO || !client_eio_retry_interval) && (op->retval != -ENOSPC || !client_retry_enospc))
{
// Fatal error (neither -EPIPE, -EIO nor -ENOSPC)
@@ -1600,6 +1607,9 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
}
}
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
{
cluster_op_t *op = part->parent;
@@ -1608,15 +1618,10 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
{
// Operation failed, retry
part->flags |= PART_ERROR;
if (!op->retval || op->retval == -EPIPE ||
part->op.reply.hdr.retval == -ENOSPC && op->retval == -ETIMEDOUT ||
part->op.reply.hdr.retval == -EIO)
{
// Error priority: EIO > ENOSPC > ETIMEDOUT > EPIPE
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
op->retval = part->op.reply.hdr.retval;
}
uint64_t stop_client_id = 0;
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
{
stop_client_id = part->op.client_id;
if (op->retval != -EPIPE || log_level > 0)
@@ -1678,13 +1683,6 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
}
if (op->inflight_count == 0 && !op->retry_after)
{
// Copy part bitmaps only after finishing all part reads
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
{
for (auto & part: op->parts)
if ((part.flags & (PART_SENT|PART_VALID|PART_DONE)) == (PART_SENT|PART_VALID|PART_DONE))
copy_part_bitmap(op, &part);
}
if (op->opcode == OSD_OP_SYNC)
continue_sync(op);
else
+1 -9
View File
@@ -104,9 +104,6 @@ struct vault_load_key_t
// FIXME: Split into public and private interfaces
class __attribute__((visibility("default"))) cluster_client_t
{
#ifdef __MOCK__
public:
#endif
timerfd_manager_t *tfd = NULL;
ring_loop_t *ringloop = NULL;
@@ -200,12 +197,7 @@ public:
void list_inode(inode_t inode, uint64_t min_offset, uint64_t max_offset, int max_parallel_pgs, std::function<void(
int status, int pgs_left, pg_num_t pg_num, std::set<object_id>&& objects)> pg_callback);
//inline uint32_t get_bs_bitmap_granularity() { return st_cli.global_bitmap_granularity; }
//inline uint64_t get_bs_block_size() { return st_cli.global_block_size; }
#ifndef __MOCK__
protected:
#endif
void continue_ops(int time_passed = 0);
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
@@ -215,7 +207,6 @@ protected:
void vault_destroy();
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
protected:
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
@@ -258,4 +249,5 @@ protected:
osd_num_t select_nearest_osd(const std::vector<osd_num_t> & osds);
friend class writeback_cache_t;
friend class cluster_client_test_t;
};
+9 -3
View File
@@ -88,6 +88,11 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
// ...or just save it for writeback if write buffering is enabled
if (op->len == 0)
{
// FIXME: OSD_OP_DELETEs are currently only sent by vitastor-cli rm/rm-data and
// actually have len=0, because delete is actually a delete of the full object
// containing the requested offset, not a "punch hole" operation. But here, writeback
// cache assumes it IS a "punch hole" operation. I should select one of these
// approaches and fix everything accordingly when I decide to implement TRIM.
return;
}
auto dirty_it = find_dirty(op->inode, op->offset);
@@ -244,12 +249,13 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
writeback_queue_size--;
}
}
if (!is_del)
if (!is_del && op->len > 0)
{
uint64_t pos = 0, len = op->len, iov_idx = 0;
while (len > 0 && iov_idx < op->iov.count)
while (iov_idx < op->iov.count)
{
auto & iov = op->iov.buf[iov_idx];
assert(pos + iov.iov_len <= len);
memcpy(buf + pos, iov.iov_base, iov.iov_len);
pos += iov.iov_len;
iov_idx++;
@@ -443,7 +449,7 @@ void writeback_cache_t::start_writebacks(cluster_client_t *cli, int count)
started++;
assert(writeback_queue_size > 0);
writeback_queue_size--;
writeback_bytes -= off - from_it->first.stripe;
writeback_bytes -= (is_del ? 0 : off - from_it->first.stripe);
assert(writeback_queue_size > 0 || !writeback_bytes);
flush_buffers(cli, from_it, to_it);
}
+91 -25
View File
@@ -12,6 +12,7 @@
#include "http_client.h"
#endif
#include "str_util.h"
#include "json_util.h"
etcd_state_client_t::~etcd_state_client_t()
{
@@ -80,6 +81,46 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
return addrs;
}
std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & username)
{
auto user_it = user_info.find(username);
if (user_it != user_info.end())
{
return user_it->second;
}
auto inf = std::make_shared<user_info_t>();
inf->name = username;
return inf;
}
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
{
if (user_info->type == user_type_t::ADMIN)
{
return true;
}
auto cache_it = user_info->perm_cache.find(inode_num);
if (cache_it != user_info->perm_cache.end() &&
cache_it->second.mod_revision == user_perm_cache_revision)
{
return write ? (cache_it->second.perm == user_perm_t::OWNER) : (cache_it->second.perm != user_perm_t::DENY);
}
auto inode_it = inode_config.find(inode_num);
if (inode_it == inode_config.end())
{
return false;
}
// FIXME Implement cache reset after reworking etcd interaction to not keep everything in memory
auto & perm_item = user_info->perm_cache[inode_num];
perm_item.mod_revision = user_perm_cache_revision;
perm_item.perm = (user_info->name == inode_it->second.owner || inode_it->second.owner_group != "" &&
user_info->groups.find(inode_it->second.owner_group) != user_info->groups.end()
? user_perm_t::OWNER : (inode_it->second.reader_group != "" &&
user_info->groups.find(inode_it->second.reader_group) != user_info->groups.end()
? user_perm_t::READER : user_perm_t::DENY));
return write ? (perm_item.perm == user_perm_t::OWNER) : (perm_item.perm != user_perm_t::DENY);
}
http_context_t *etcd_state_client_t::get_http_ctx()
{
if (!http_ctx)
@@ -250,19 +291,23 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
add_etcd_url(ea.string_value());
}
}
if (this->osd_num)
{
this->etcd_client_cert = config["osd_etcd_client_cert"].string_value();
this->etcd_client_key = config["osd_etcd_client_key"].string_value();
}
else
if (this->etcd_client_cert != "")
{
this->etcd_client_cert = config["etcd_client_cert"].string_value();
this->etcd_client_key = config["etcd_client_key"].string_value();
}
else if (this->osd_num)
{
this->etcd_client_cert = config["osd_cert"].string_value();
this->etcd_client_key = config["osd_pkey"].string_value();
}
else
{
this->etcd_client_cert = config["cert"].string_value();
this->etcd_client_key = config["pkey"].string_value();
}
this->etcd_ca = config["etcd_ca"].string_value();
this->etcd_prefix = config["etcd_prefix"].string_value();
this->use_auth = config["use_auth"].bool_value();
if (this->etcd_prefix == "")
{
this->etcd_prefix = "/vitastor";
@@ -730,12 +775,8 @@ void etcd_state_client_t::load_pgs()
json11::Json::array txn = {
json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(etcd_prefix+"/config/pools") },
} }
},
json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(etcd_prefix+"/config/pgs") },
{ "key", base64_encode(etcd_prefix+"/config/") },
{ "range_end", base64_encode(etcd_prefix+"/config0") },
} }
},
json11::Json::object {
@@ -743,12 +784,6 @@ void etcd_state_client_t::load_pgs()
{ "key", base64_encode(etcd_prefix+"/pg/config") },
} }
},
json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(etcd_prefix+"/config/inode/") },
{ "range_end", base64_encode(etcd_prefix+"/config/inode0") },
} }
},
json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(etcd_prefix+"/pg/history/") },
@@ -1263,7 +1298,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
if (i >= pg_state_bit_count)
{
fprintf(stderr, "Unexpected pool %u PG %u state keyword in etcd: %s\n", pool_id, pg_num, e.dump().c_str());
return;
}
}
if (!cur_primary || !value["state"].is_array() || !state ||
@@ -1272,7 +1306,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
(state & PG_INCOMPLETE) && state != PG_INCOMPLETE && state != (PG_INCOMPLETE|PG_HAS_INVALID))
{
fprintf(stderr, "Unexpected pool %u PG %u state in etcd: primary=%ju, state=%s\n", pool_id, pg_num, cur_primary, value["state"].dump().c_str());
return;
}
pg_cfg.cur_primary = cur_primary;
pg_cfg.cur_state = state;
@@ -1339,6 +1372,10 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
{
on_inode_change_hook(inode_num, true);
}
if (this->inode_config.find(inode_num) != this->inode_config.end())
{
user_perm_cache_revision = kv.mod_revision;
}
this->inode_config.erase(inode_num);
}
else
@@ -1354,13 +1391,37 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
if (on_change_node_placement_hook)
on_change_node_placement_hook();
}
else if (use_auth && key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
{
// <etcd_prefix>/config/user/<username>
auto name = key.substr(etcd_prefix.length()+13);
auto & inf = user_info[name];
if (!value.is_object())
user_info.erase(key.substr(etcd_prefix.length()+13));
{
if (inf)
{
inf->type = user_type_t::CLIENT;
inf->groups.clear();
inf->perm_cache.clear();
}
user_info.erase(name);
}
else
user_info[key.substr(etcd_prefix.length()+13)] = value;
{
if (!inf)
{
inf = std::make_shared<user_info_t>();
inf->name = name;
}
inf->type = value["type"] == "admin" ? user_type_t::ADMIN : user_type_t::CLIENT;
inf->groups.clear();
for (auto & group: value["groups"].array_items())
{
if (group.string_value() != "")
inf->groups.insert(group.string_value());
}
inf->perm_cache.clear();
}
}
}
@@ -1384,7 +1445,12 @@ uint32_t etcd_state_client_t::parse_scheme(const std::string & scheme)
void etcd_state_client_t::insert_inode_config(const inode_config_t & cfg)
{
this->inode_config[cfg.num] = cfg;
auto & cfg_ref = this->inode_config[cfg.num];
if (cfg_ref.mod_revision != cfg.mod_revision)
{
user_perm_cache_revision = cfg.mod_revision;
}
cfg_ref = cfg;
if (cfg.name != "")
{
this->inode_by_name[cfg.name] = cfg.num;
+29 -2
View File
@@ -9,6 +9,7 @@
#include "json11/json11.hpp"
#include "object_id.h"
#include "timerfd_manager.h"
#include "../util/robin_hood.h"
#define ETCD_CONFIG_WATCH_ID 1
#define ETCD_OSD_STATE_WATCH_ID 2
@@ -110,6 +111,30 @@ struct http_url_t
std::string path;
};
enum class user_type_t
{
CLIENT = 0,
ADMIN = 1,
};
struct user_perm_t
{
enum class perm_type_t: uint8_t;
constexpr static perm_type_t DENY = (perm_type_t)0;
constexpr static perm_type_t READER = (perm_type_t)1;
constexpr static perm_type_t OWNER = (perm_type_t)2;
uint64_t mod_revision = 0;
perm_type_t perm = DENY;
};
struct user_info_t
{
std::string name;
user_type_t type;
robin_hood::unordered_flat_set<std::string> groups;
robin_hood::unordered_flat_map<inode_t, user_perm_t> perm_cache;
};
struct http_co_t;
struct http_context_t;
@@ -145,7 +170,6 @@ public:
int etcd_slow_timeout = 5000;
int etcd_min_reload_interval = 1000;
bool infinite_start = true;
bool use_auth = false;
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
uint32_t global_immediate_commit = IMMEDIATE_NONE;
@@ -171,7 +195,8 @@ public:
std::set<osd_num_t> seen_peers;
std::map<inode_t, inode_config_t> inode_config;
std::map<std::string, inode_t> inode_by_name;
std::map<std::string, json11::Json> user_info;
robin_hood::unordered_flat_map<std::string, std::shared_ptr<user_info_t>> user_info;
uint64_t user_perm_cache_revision = 0;
json11::Json node_placement;
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
@@ -193,6 +218,8 @@ public:
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
std::vector<std::string> get_addresses();
std::shared_ptr<user_info_t> get_user(const std::string & username);
bool check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write);
http_context_t *get_http_ctx();
void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
+1 -99
View File
@@ -19,6 +19,7 @@
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include "openssl_util.h"
#endif
// libc-ares
@@ -163,105 +164,6 @@ void http_ares_cb(void *data, ares_socket_t socket_fd, int readable, int writabl
});
}
#ifdef WITH_OPENSSL
bool openssl_ctx_add_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
{
std::string pem;
BIO *bio = NULL;
if (file_or_pem.substr(0, 5) != "-----")
{
pem = read_file(file_or_pem);
bio = BIO_new_mem_buf(pem.data(), pem.size());
}
else
bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size());
if (!bio)
return false;
X509 *x509 = PEM_read_bio_X509(bio, NULL, 0, NULL);
bool ok = !!x509;
if (x509)
{
X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
X509_STORE_add_cert(store, x509);
X509_free(x509);
}
BIO_free(bio);
return ok;
}
bool openssl_ctx_use_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
{
if (file_or_pem.substr(0, 5) == "-----")
{
return openssl_ctx_add_ca(ssl_ctx, file_or_pem);
}
return file_or_pem.empty()
? !!SSL_CTX_set_default_verify_paths(ssl_ctx)
: !!SSL_CTX_load_verify_locations(ssl_ctx, file_or_pem.c_str(), NULL);
}
std::string openssl_get_cn(X509 *x509)
{
X509_NAME* subj = X509_get_subject_name(x509);
int pos = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
if (pos != -1)
{
X509_NAME_ENTRY* cn = X509_NAME_get_entry(subj, pos);
ASN1_STRING* str = X509_NAME_ENTRY_get_data(cn);
return std::string((const char*)ASN1_STRING_get0_data(str), ASN1_STRING_length(str));
}
return "";
}
bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std::string & common_name)
{
BIO *bio = NULL;
std::string contents;
if (file_or_pem.substr(0, 5) == "-----")
bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size());
else
{
contents = read_file(file_or_pem);
if (!contents.size())
return false;
bio = BIO_new_mem_buf(contents.data(), contents.size());
}
if (!bio)
return false;
X509 *x509 = PEM_read_bio_X509(bio, NULL, 0, NULL);
bool ok = !!x509;
if (x509)
{
ok = SSL_CTX_use_certificate(ssl_ctx, x509);
if (ok)
common_name = openssl_get_cn(x509);
X509_free(x509);
}
BIO_free(bio);
return ok;
}
bool openssl_ctx_use_key(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
{
if (file_or_pem.substr(0, 5) == "-----")
{
BIO *bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size());
if (!bio)
return false;
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
bool ok = !!pkey;
if (pkey)
{
ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
EVP_PKEY_free(pkey);
}
BIO_free(bio);
return ok;
}
return !!SSL_CTX_use_PrivateKey_file(ssl_ctx, file_or_pem.c_str(), SSL_FILETYPE_PEM);
}
#endif
http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key,
const std::string & ssl_ca, bool verify_peer, std::string & error)
{
-12
View File
@@ -8,10 +8,6 @@
#include <functional>
#include "json11/json11.hpp"
#ifdef WITH_OPENSSL
#include <openssl/types.h>
#endif
#define WS_CONTINUATION 0
#define WS_TEXT 1
#define WS_BINARY 2
@@ -73,11 +69,3 @@ void http_close(http_co_t *co);
void http_destroy(http_co_t *co);
#pragma GCC visibility pop
#ifdef WITH_OPENSSL
bool openssl_ctx_add_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem);
bool openssl_ctx_use_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem);
std::string openssl_get_cn(X509 *x509);
bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std::string & common_name);
bool openssl_ctx_use_key(SSL_CTX *ssl_ctx, const std::string & file_or_pem);
#endif
+93 -272
View File
@@ -15,160 +15,10 @@
#ifdef WITH_RDMA
#include "msgr_rdma.h"
#endif
#include "http_client.h"
#ifdef WITH_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#endif
#include <sys/poll.h>
msgr_iothread_t::msgr_iothread_t():
ring(RINGLOOP_DEFAULT_SIZE, true),
thread(&msgr_iothread_t::run, this)
{
eventfd = ring.register_eventfd();
if (eventfd < 0)
{
throw std::runtime_error(std::string("failed to register eventfd: ") + strerror(-eventfd));
}
}
msgr_iothread_t::~msgr_iothread_t()
{
stop();
}
void msgr_iothread_t::add_sqe(io_uring_sqe & sqe)
{
mu.lock();
queue.push_back((iothread_sqe_t){ .sqe = sqe, .data = std::move(*(ring_data_t*)sqe.user_data) });
if (queue.size() == 1)
{
cond.notify_all();
}
mu.unlock();
}
void msgr_iothread_t::stop()
{
mu.lock();
if (stopped)
{
mu.unlock();
return;
}
stopped = true;
if (outer_loop_data)
{
outer_loop_data->callback = [](ring_data_t*){};
}
cond.notify_all();
close(eventfd);
mu.unlock();
thread.join();
}
void msgr_iothread_t::add_to_ringloop(ring_loop_t *outer_loop)
{
assert(!this->outer_loop || this->outer_loop == outer_loop);
io_uring_sqe *sqe = outer_loop->get_sqe();
assert(sqe != NULL);
this->outer_loop = outer_loop;
this->outer_loop_data = ((ring_data_t*)sqe->user_data);
io_uring_prep_poll_add(sqe, eventfd, POLLIN);
outer_loop_data->callback = [this](ring_data_t *data)
{
if (data->res < 0)
{
throw std::runtime_error(std::string("eventfd poll failed: ") + strerror(-data->res));
}
outer_loop_data = NULL;
if (stopped)
{
return;
}
add_to_ringloop(this->outer_loop);
ring.loop();
};
}
void msgr_iothread_t::run()
{
while (true)
{
{
std::unique_lock<std::mutex> lk(mu);
while (!stopped && !queue.size())
cond.wait(lk);
if (stopped)
return;
int i = 0;
for (; i < queue.size(); i++)
{
io_uring_sqe *sqe = ring.get_sqe();
if (!sqe)
break;
ring_data_t *data = ((ring_data_t*)sqe->user_data);
*data = std::move(queue[i].data);
*sqe = queue[i].sqe;
sqe->user_data = (uint64_t)data;
}
queue.erase(queue.begin(), queue.begin()+i);
}
// We only want to offload sendmsg/recvmsg. Callbacks will be called in main thread
ring.submit();
}
}
void osd_messenger_t::init()
{
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
{
// Initialize TLS context
// FIXME: require OpenSSL
#ifndef WITH_OPENSSL
fprintf(stderr, "Vitastor is built without OpenSSL support\n");
exit(1);
#else
if (tls_cert.empty() || tls_key.empty() || osd_tls_ca.empty() || osd_num && client_tls_ca.empty())
{
if (osd_num)
fprintf(stderr, "Vitastor OSD TLS requires osd_tls_cert, osd_tls_key, osd_tls_ca, client_tls_ca\n");
else
fprintf(stderr, "Vitastor client TLS requires tls_cert, tls_key and osd_tls_ca\n");
exit(1);
}
else
{
ssl_ctx = SSL_CTX_new(TLS_method());
if (!ssl_ctx)
{
init_err:
fprintf(stderr, "OpenSSL initialization failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
exit(1);
}
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
bool ok = SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
ok = ok && openssl_ctx_add_ca(ssl_ctx, osd_tls_ca);
if (osd_num)
{
// OSD uses 2 separate root certificates to distinguish between clients and peer OSDs
ok = ok && openssl_ctx_add_ca(ssl_ctx, client_tls_ca);
}
ok = ok && openssl_ctx_use_cert(ssl_ctx, tls_cert, tls_cn);
ok = ok && openssl_ctx_use_key(ssl_ctx, tls_key);
if (!ok)
{
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
goto init_err;
}
}
#endif
}
init_tls();
#ifdef WITH_RDMACM
if (use_rdmacm)
{
@@ -225,12 +75,7 @@ init_err:
}
if (ringloop && iothread_count > 0)
{
for (int i = 0; i < iothread_count; i++)
{
auto iot = new msgr_iothread_t();
iothreads.push_back(iot);
iot->add_to_ringloop(ringloop);
}
init_iothreads();
}
keepalive_timer_id = tfd->set_timer(1000, true, [this](int)
{
@@ -272,7 +117,7 @@ init_err:
.opcode = OSD_OP_PING,
},
};
op->callback = [this, cl](osd_op_t *op)
op->callback = [this](osd_op_t *op)
{
auto cl_it = clients.find(op->client_id);
if (cl_it == clients.end())
@@ -281,6 +126,7 @@ init_err:
delete op;
return;
}
auto cl = cl_it->second;
uint64_t fail_client_id = (op->reply.hdr.retval != 0 ? op->client_id : 0);
auto fail_osd_num = cl->in_osd_num ? cl->in_osd_num : cl->osd_num;
cl->ping_time_remaining = 0;
@@ -323,14 +169,7 @@ osd_messenger_t::~osd_messenger_t()
{
stop_client(clients.begin()->first, true);
}
if (iothreads.size())
{
for (auto iot: iothreads)
{
delete iot;
}
iothreads.clear();
}
destroy_iothreads();
#ifdef WITH_RDMA
for (auto rdma_context: rdma_contexts)
{
@@ -347,25 +186,67 @@ osd_messenger_t::~osd_messenger_t()
rdmacm_evch = NULL;
}
#endif
for (auto encrypt_ctx: encrypt_ctx_pool)
for (auto encrypt_ctx: encrypt_xts_pool)
{
destroy_aes_xts_encrypt(encrypt_ctx);
}
for (auto decrypt_ctx: decrypt_ctx_pool)
for (auto decrypt_ctx: decrypt_xts_pool)
{
destroy_aes_xts_decrypt(decrypt_ctx);
}
#ifdef WITH_OPENSSL
if (ssl_ctx)
{
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
}
#endif
destroy_tls();
}
void osd_messenger_t::parse_config(const json11::Json & config)
static int parse_proto_checksums(const json11::Json & val, int default_value)
{
if (val.is_string())
{
const auto & str = val.string_value();
if (str == "full")
return MSGR_CSUM_FULL;
else if (str == "payload")
return MSGR_CSUM_PAYLOAD;
else if (str == "gcm")
return MSGR_CSUM_GCM;
else if (str == "none")
return 0;
else if (str == "")
return default_value;
}
else if (val.is_null())
return default_value;
fprintf(stderr, "proto_checksums should be \"full\", \"payload\", \"gcm\", \"none\""
", \"\" or null (default), but it is: %s\n", val.dump().c_str());
exit(1);
}
void osd_messenger_t::parse_config(const json11::Json & config, bool init)
{
this->max_cipher_pool_size = config["max_cipher_pool_size"].uint64_value();
if (!this->max_cipher_pool_size)
this->max_cipher_pool_size = 256;
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
this->receive_buffer_size = 65536;
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
: (int)config["min_zerocopy_send_size"].int64_value();
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
if (!this->peer_connect_interval)
this->peer_connect_interval = 5;
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
if (!this->peer_connect_timeout)
this->peer_connect_timeout = 5;
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
if (!this->osd_idle_timeout)
this->osd_idle_timeout = 5;
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
if (!this->osd_ping_timeout)
this->osd_ping_timeout = 5;
this->log_level = config["log_level"].uint64_value();
// All other parameters are only set on init
if (!init)
return;
#ifdef WITH_RDMA
if (!config["use_rdma"].is_null())
{
@@ -396,58 +277,27 @@ void osd_messenger_t::parse_config(const json11::Json & config)
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
this->rdma_max_msg = 129*1024;
#endif
this->max_aes_xts_pool_size = config["max_aes_xts_pool_size"].uint64_value();
if (!this->max_aes_xts_pool_size)
this->max_aes_xts_pool_size = 256;
if (config["proto_checksums"].is_null())
this->use_proto_checksums = MSGR_CSUM_PAYLOAD;
else if (config["proto_checksums"].is_bool())
this->use_proto_checksums = config["proto_checksums"].bool_value() ? MSGR_CSUM_FULL : 0;
else if (config["proto_checksums"].string_value() != "")
this->use_proto_checksums = config["proto_checksums"].string_value() == "full" ? MSGR_CSUM_FULL : MSGR_CSUM_PAYLOAD;
else
this->use_proto_checksums = 0;
if (!osd_num)
{
tls_cert = config["tls_cert"].string_value();
tls_key = config["tls_key"].string_value();
osd_tls_ca = config["osd_tls_ca"].string_value();
tls_cert = config["cert"].string_value();
tls_key = config["pkey"].string_value();
osd_tls_ca = config["osd_ca"].string_value();
}
else
{
tls_cert = config["osd_tls_cert"].string_value();
tls_key = config["osd_tls_key"].string_value();
osd_tls_ca = config["osd_tls_ca"].string_value();
client_tls_ca = config["client_tls_ca"].string_value();
tls_cert = config["osd_cert"].string_value();
tls_key = config["osd_pkey"].string_value();
osd_tls_ca = config["osd_ca"].string_value();
client_tls_ca = config["client_ca"].string_value();
}
test_osd_aes_key.resize(32);
if (fromhexstr(config["test_osd_aes_key"].string_value(), 32, (uint8_t*)test_osd_aes_key.data()) != 32)
test_osd_aes_key.clear();
this->use_proto_checksums = parse_proto_checksums(config["proto_checksums"], MSGR_CSUM_PAYLOAD);
this->force_proto_checksums = parse_proto_checksums(config["force_proto_checksums"], tls_cert != "" ? MSGR_CSUM_PAYLOAD : 0);
if (!osd_num)
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
else
this->iothread_count = (uint32_t)config["osd_iothread_count"].uint64_value();
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
this->receive_buffer_size = 65536;
this->use_sync_send_recv = config["use_sync_send_recv"].bool_value() ||
config["use_sync_send_recv"].uint64_value() || !ringloop;
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
: (int)config["min_zerocopy_send_size"].int64_value();
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
if (!this->peer_connect_interval)
this->peer_connect_interval = 5;
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
if (!this->peer_connect_timeout)
this->peer_connect_timeout = 5;
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
if (!this->osd_idle_timeout)
this->osd_idle_timeout = 5;
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
if (!this->osd_ping_timeout)
this->osd_ping_timeout = 5;
this->log_level = config["log_level"].uint64_value();
// OSD public & cluster networks
this->osd_networks.clear();
if (config["osd_network"].is_string())
@@ -653,7 +503,7 @@ void osd_messenger_t::handle_connect_epoll(int peer_fd)
handle_peer_epoll(peer_fd, epoll_events);
});
// Check OSD number
ssl_init(cl, false);
init_tls_client(cl);
check_peer_config(cl);
}
@@ -788,6 +638,11 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
err = true;
fprintf(stderr, "Failed to get config from OSD %ju (retval=%jd), disconnecting peer\n", cl->osd_num, op->reply.hdr.retval);
}
else if (cl->gcm_enabled && !cl->hs_result.peer_is_osd)
{
err = true;
fprintf(stderr, "Client %ju is not authenticated as an OSD, disconnecting peer\n", cl->client_id);
}
else
{
config = json11::Json::parse(std::string((char*)op->buf), json_err);
@@ -815,6 +670,21 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
err = !check_config_hook(cl, config);
}
}
if (!err && use_proto_checksums)
{
auto peer_csums = config["features"]["proto_checksums"].uint64_value();
if (peer_csums == MSGR_CSUM_GCM && use_proto_checksums == MSGR_CSUM_GCM && cl->gcm_enabled)
cl->proto_csum_status = MSGR_CSUM_GCM;
else if (peer_csums == MSGR_CSUM_FULL && use_proto_checksums == MSGR_CSUM_FULL)
cl->proto_csum_status = MSGR_CSUM_FULL;
else if (peer_csums && use_proto_checksums)
cl->proto_csum_status = MSGR_CSUM_PAYLOAD;
if (cl->proto_csum_status < force_proto_checksums)
{
fprintf(stderr, "Error: OSD %ju use_proto_checksums security level is lower than force_proto_checksums\n", cl->osd_num);
err = true;
}
}
if (err)
{
osd_num_t peer_osd = cl->osd_num;
@@ -823,14 +693,6 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
delete op;
return;
}
if (use_proto_checksums)
{
auto peer_csums = config["features"]["proto_checksums"].uint64_value();
if (peer_csums == MSGR_CSUM_FULL && use_proto_checksums == MSGR_CSUM_FULL)
cl->proto_csum_status = MSGR_CSUM_FULL;
else if (peer_csums && use_proto_checksums)
cl->proto_csum_status = MSGR_CSUM_PAYLOAD;
}
#ifdef WITH_RDMA
if (!use_rdmacm && cl->rdma_conn && config["rdma_address"].is_string())
{
@@ -895,7 +757,7 @@ void osd_messenger_t::accept_connections(int listen_fd)
cl->peer_fd = peer_fd;
cl->peer_state = PEER_CONNECTED;
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
ssl_init(cl, true);
init_tls_client(cl);
// Add FD to epoll
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
{
@@ -910,52 +772,6 @@ void osd_messenger_t::accept_connections(int listen_fd)
}
}
void osd_messenger_t::ssl_init(osd_client_t *cl, bool server_mode)
{
if (!tls_cert.empty())
{
cl->write_to_ssl = BIO_new(BIO_s_mem());
cl->read_from_ssl = BIO_new(BIO_s_mem());
cl->ssl_cli = SSL_new(ssl_ctx);
if (!cl->ssl_cli)
{
fprintf(stderr, "OpenSSL initialization failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
exit(1);
}
if (server_mode)
{
SSL_set_accept_state(cl->ssl_cli);
}
else
{
SSL_set_connect_state(cl->ssl_cli);
}
SSL_set_bio(cl->ssl_cli, cl->write_to_ssl, cl->read_from_ssl);
bool ok = ssl_do_handshake(cl);
assert(ok);
}
else if (!test_osd_aes_key.empty())
{
int r;
cl->enc_ctx = EVP_CIPHER_CTX_new();
assert(cl->enc_ctx);
r = EVP_EncryptInit_ex(cl->enc_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
assert(r == 1);
r = EVP_CIPHER_CTX_set_padding(cl->enc_ctx, 0);
assert(r == 1);
r = EVP_CIPHER_CTX_ctrl(cl->enc_ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
assert(r == 1);
cl->dec_ctx = EVP_CIPHER_CTX_new();
assert(cl->dec_ctx);
r = EVP_DecryptInit_ex(cl->dec_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
assert(r == 1);
r = EVP_CIPHER_CTX_set_padding(cl->dec_ctx, 0);
assert(r == 1);
r = EVP_CIPHER_CTX_ctrl(cl->dec_ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
assert(r == 1);
}
}
#ifdef WITH_RDMA
msgr_rdma_context_t* osd_messenger_t::choose_rdma_context(osd_client_t *cl)
{
@@ -984,6 +800,11 @@ bool osd_messenger_t::is_use_rdmacm()
}
#endif
bool osd_messenger_t::is_encryption_enabled()
{
return tls_cert != "" || tls_key != "" || osd_tls_ca != "";
}
json11::Json::object osd_messenger_t::read_config(const json11::Json & config)
{
json11::Json::object file_config;
+58 -82
View File
@@ -12,8 +12,10 @@
#include <deque>
#include <vector>
#ifdef WITH_OPENSSL
#include <openssl/types.h>
#ifdef WITH_ISAL_CRYPTO
#include <isa-l_crypto/aes_gcm.h>
#endif
#include "../util/xxh_x86dispatch.h"
@@ -23,6 +25,7 @@
#include "msgr_op.h"
#include "timerfd_manager.h"
#include "addr_util.h"
#include "msgr_handshake.h"
#include <ringloop.h>
#define CL_READ_HDR 1
@@ -38,12 +41,18 @@
#define MSGR_CSUM_PAYLOAD 1
#define MSGR_CSUM_FULL 2
#define MSGR_CSUM_NEG 4
#define MSGR_CSUM_GCM 4
#define MSGR_CSUM_NEG 8
#define VITASTOR_CONFIG_PATH "/etc/vitastor/vitastor.conf"
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
#define AES_256_GCM_KEY_SIZE 32
#define AES_256_GCM_IV_SIZE 12
#define MAX_SIMPLE_PAYLOAD_SIZE 1048576
struct msgr_sendp_t
{
osd_op_t *op;
@@ -60,13 +69,7 @@ struct op_aes_xts_decrypt_t;
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
// Standard TLS record header. We are only interested in the record size
struct __attribute__((__packed__)) msgr_tls_record_hdr_t
{
uint8_t content_type;
uint16_t version;
uint16_t size;
};
struct user_info_t;
struct osd_client_t
{
@@ -90,29 +93,24 @@ struct osd_client_t
msgr_rdma_connection_t *rdma_conn = NULL;
#endif
#ifdef WITH_OPENSSL
SSL *ssl_cli = NULL;
BIO *write_to_ssl = NULL;
// FIXME: use custom bio to avoid 1 more memory copy?
BIO *read_from_ssl = NULL;
uint8_t *ssl_out_buf = NULL;
size_t ssl_out_buf_size = 0, ssl_out_buf_cap = 0;
bool ssl_handshake_done = false;
msgr_tls_record_hdr_t ssl_read_record;
size_t ssl_read_header_size = 0;
bool ssl_more_to_buffer = false;
bool gcm_enabled = false;
msgr_handshake_i *hs = NULL;
msgr_handshake_result_t hs_result;
std::shared_ptr<user_info_t> user_info;
std::vector<uint8_t> my_key, peer_key;
uint64_t my_iv_ctr = 0, peer_iv_ctr = 0;
#ifdef WITH_ISAL_CRYPTO
isal_gcm_key_data my_key_isal, peer_key_isal;
isal_gcm_context_data *enc_ctx = NULL;
isal_gcm_context_data *dec_ctx = NULL;
#else
EVP_CIPHER_CTX *enc_ctx = NULL;
EVP_CIPHER_CTX *dec_ctx = NULL;
#endif
uint8_t enc_tag[16];
size_t enc_tag_size = 0;
bool enc_batch = false;
EVP_CIPHER_CTX *dec_ctx = NULL;
uint8_t dec_tag[16];
size_t dec_tag_size = 0;
uint32_t dec_batch_size = 0;
size_t dec_batch_size_size = 0;
std::vector<osd_op_t*> unverified_ops;
#endif
// Read state
bool io_error = false;
@@ -127,7 +125,7 @@ struct osd_client_t
uint64_t read_op_id = 1;
bool check_sequencing = false;
bool enable_pg_locks = false;
op_aes_xts_decrypt_t *decrypt_ctx = NULL;
op_aes_xts_decrypt_t *xts_dec_ctx = NULL;
size_t read_op_inline_decrypt_pos = 0;
size_t read_op_inline_decrypt_in = 0;
int proto_csum_status = 0;
@@ -153,7 +151,7 @@ struct osd_client_t
size_t send_list_size = 0;
std::deque<osd_op_t*> send_free_ops;
std::vector<osd_op_t*> zc_free_list;
op_aes_xts_encrypt_t *encrypt_ctx = NULL;
op_aes_xts_encrypt_t *xts_enc_ctx = NULL;
XXH3_state_t* write_csum_state = NULL;
~osd_client_t();
@@ -185,43 +183,7 @@ struct osd_op_stats_t
uint64_t subop_stat_count[OSD_OP_MAX+1] = { 0 };
};
#include <mutex>
#include <condition_variable>
#include <thread>
#ifdef __MOCK__
class msgr_iothread_t;
#else
struct iothread_sqe_t
{
io_uring_sqe sqe;
ring_data_t data;
};
class msgr_iothread_t
{
protected:
ring_loop_t ring;
ring_loop_t *outer_loop = NULL;
ring_data_t *outer_loop_data = NULL;
int eventfd = -1;
bool stopped = false;
std::mutex mu;
std::condition_variable cond;
std::vector<iothread_sqe_t> queue;
std::thread thread;
void run();
public:
msgr_iothread_t();
~msgr_iothread_t();
void add_sqe(io_uring_sqe & sqe);
void stop();
void add_to_ringloop(ring_loop_t *outer_loop);
};
#endif
#ifdef WITH_RDMA
struct rdma_event_channel;
@@ -239,11 +201,9 @@ struct __attribute__((visibility("default"))) osd_messenger_t
{
protected:
friend class copy_op_reader_t;
friend class ssl_op_reader_t;
friend class gcm_op_reader_t;
friend class get_op_reader_t;
friend class copy_op_writer_t;
friend class ssl_op_writer_t;
friend class gcm_op_writer_t;
friend class get_op_writer_t;
@@ -258,13 +218,12 @@ protected:
bool use_sync_send_recv = false;
int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE;
int iothread_count = 0;
int max_aes_xts_pool_size = 256;
int max_cipher_pool_size = 256;
std::string tls_cert;
std::string tls_key;
std::string osd_tls_ca;
std::string client_tls_ca;
std::string test_osd_aes_key; // FIXME Insecure, only for PoC tests
#ifdef WITH_RDMA
bool use_rdma = true;
@@ -282,13 +241,14 @@ protected:
robin_hood::unordered_flat_map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
#endif
#ifdef WITH_OPENSSL
SSL_CTX *ssl_ctx = NULL;
std::string tls_cn;
bool gcm_enabled = false;
msgr_handshake_ctx_i *hs_ctx = NULL;
void ssl_init(osd_client_t *cl, bool server_mode);
bool ssl_do_handshake(osd_client_t *cl);
#endif
void init_tls();
void destroy_tls();
void init_tls_client(osd_client_t *cl);
bool do_tls_handshake(osd_client_t *cl, bool from_recv = false);
bool derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer);
std::vector<msgr_iothread_t*> iothreads;
std::vector<uint64_t> read_ready_clients;
@@ -296,16 +256,27 @@ protected:
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
std::deque<osd_op_t*> set_immediate_ops;
std::vector<op_aes_xts_encrypt_t*> encrypt_ctx_pool;
std::vector<op_aes_xts_decrypt_t*> decrypt_ctx_pool;
std::vector<op_aes_xts_encrypt_t*> encrypt_xts_pool;
std::vector<op_aes_xts_decrypt_t*> decrypt_xts_pool;
#ifdef WITH_ISAL_CRYPTO
std::vector<isal_gcm_context_data*> encrypt_gcm_pool;
std::vector<isal_gcm_context_data*> decrypt_gcm_pool;
#else
std::vector<EVP_CIPHER_CTX*> encrypt_gcm_pool;
std::vector<EVP_CIPHER_CTX*> decrypt_gcm_pool;
#endif
public:
timerfd_manager_t *tfd = NULL;
ring_loop_t *ringloop = NULL;
ring_loop_i *ringloop = NULL;
bool has_sendmsg_zc = false;
// osd_num_t is only for logging and asserts
uint64_t next_client_id = 1;
osd_num_t osd_num;
// osd_num = 0 for client messenger, osd_num > 0 for OSD messenger
osd_num_t osd_num = 0;
uint32_t clean_entry_bitmap_size = 0;
uint32_t bs_block_size = 0;
uint32_t max_write_request_size = 0;
robin_hood::unordered_flat_map<uint64_t, osd_client_t*> clients;
robin_hood::unordered_flat_map<uint64_t, osd_client_t*> osd_peers;
robin_hood::unordered_flat_map<int, osd_client_t*> clients_by_fd;
@@ -317,11 +288,13 @@ public:
std::vector<std::string> all_osd_networks;
std::vector<addr_mask_t> all_osd_network_masks;
int use_proto_checksums = 0;
int force_proto_checksums = 0;
// op statistics
osd_op_stats_t stats, recovery_stats;
void init();
void parse_config(const json11::Json & config);
void init_iothreads();
void parse_config(const json11::Json & config, bool init);
void connect_peer(uint64_t osd_num, json11::Json peer_state);
void stop_client(uint64_t client_id, bool force_delete = false);
void destroy_client(osd_client_t *cl);
@@ -330,9 +303,11 @@ public:
std::function<void(osd_num_t)> repeer_pgs;
std::function<void(osd_num_t)> break_pg_locks;
std::function<bool(osd_client_t*, json11::Json)> check_config_hook;
std::function<void(osd_client_t*)> handshake_hook;
void read_requests();
void send_replies();
void accept_connections(int listen_fd);
void destroy_iothreads();
~osd_messenger_t();
static json11::Json::object read_config(const json11::Json & config);
@@ -350,6 +325,7 @@ public:
rdma_cm_id *rdmacm_listen(const std::string & bind_address, int rdmacm_port, int *bound_port, int log_level);
void rdmacm_destroy_listener(rdma_cm_id *listener);
#endif
bool is_encryption_enabled();
void inc_op_stats(osd_op_stats_t & stats, uint64_t opcode, timespec & tv_begin, timespec & tv_end, uint64_t len);
void measure_exec(osd_op_t *cur_op);
@@ -381,7 +357,6 @@ protected:
bool allocate_reply_buffers(osd_client_t *cl, osd_op_t *op);
bool op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr);
bool handle_finished_op(osd_client_t *cl);
void execute_verified_op(osd_client_t *cl, osd_op_t *op);
void handle_immediate_ops();
void op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
@@ -396,6 +371,7 @@ protected:
bool init_recv_rdma(osd_client_t *cl);
void handle_rdma_events(msgr_rdma_context_t *rdma_context);
msgr_rdma_context_t* choose_rdma_context(osd_client_t *cl);
void destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn);
#endif
#ifdef WITH_RDMACM
void handle_rdmacm_events();
+252 -58
View File
@@ -3,13 +3,23 @@
#include <assert.h>
#ifdef WITH_ISAL_CRYPTO
#include <isa-l_crypto/isal_crypto_api.h>
#endif
#include "str_util.h"
#include "etcd_state_client.h"
#include "messenger.h"
#include "msgr_encrypt.h"
#include "http_client.h"
#include "openssl_util.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
@@ -21,25 +31,23 @@ op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
ERR_print_errors_fp(stderr);
abort();
}
#else
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
abort();
#endif
}
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
{
assert(!encrypted);
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX_free(ctx);
#endif
if (tmp)
free(tmp);
}
void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t block_size)
void op_aes_xts_encrypt_t::start(osd_client_t *cl, uint8_t *key, uint64_t start_offset, size_t block_size)
{
assert(!encrypted);
this->cl = cl;
this->start_offset = start_offset;
this->key = key;
this->block_size = block_size;
@@ -52,7 +60,7 @@ void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t blo
tmp = NULL;
tmp_size = 0;
}
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL) != 1)
{
ERR_print_errors_fp(stderr);
@@ -63,9 +71,12 @@ void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t blo
void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
{
#ifdef WITH_OPENSSL
uint8_t iv[16] = { 0 };
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_xts_enc_256(key+32, key, iv, block_size, in, out);
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
#else
if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) != 1)
{
ERR_print_errors_fp(stderr);
@@ -81,6 +92,28 @@ void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
#endif
}
static inline void copy_or_gcm(osd_client_t *cl, uint8_t *out, uint8_t *in, size_t n)
{
if (cl->proto_csum_status != MSGR_CSUM_GCM)
memcpy(out, in, n);
else
{
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, out, in, n);
assert(!r);
#else
int actual_out;
if (EVP_EncryptUpdate(cl->enc_ctx, out, &actual_out, in, n) != 1)
{
fprintf(stderr, "EncryptUpdate error: ");
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == n);
#endif
}
}
void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
{
// Fucking AES-XTS implementations (all of them) don't have streaming support,
@@ -94,11 +127,14 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
assert(tmp);
if (max_out > block_size - tmp_pos)
max_out = block_size - tmp_pos;
memcpy(out, tmp + tmp_pos, max_out);
copy_or_gcm(cl, out, tmp + tmp_pos, max_out);
done_out += max_out;
tmp_pos += max_out;
if (tmp_pos >= block_size)
{
encrypted = false;
done_in += 1;
}
}
else if (max_in < block_size - offset%block_size)
{
@@ -124,9 +160,9 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
memcpy(tmp + offset%block_size, in, max_in);
encrypt_block(tmp, tmp);
encrypted = true;
memcpy(out, tmp, max_out);
copy_or_gcm(cl, out, tmp, max_out);
tmp_pos = max_out;
done_in += max_in;
done_in += max_in-1;
offset += max_in;
done_out += max_out;
}
@@ -134,6 +170,8 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
{
// Full block - simplest case
encrypt_block(in, out);
if (cl->proto_csum_status == MSGR_CSUM_GCM)
copy_or_gcm(cl, out, out, block_size);
done_in += block_size;
offset += block_size;
done_out += block_size;
@@ -145,6 +183,8 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
encrypt_block(tmp, out);
if (cl->proto_csum_status == MSGR_CSUM_GCM)
copy_or_gcm(cl, out, out, block_size);
done_in += max_in;
offset += max_in;
done_out += block_size;
@@ -158,7 +198,7 @@ void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
@@ -170,29 +210,27 @@ op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
ERR_print_errors_fp(stderr);
abort();
}
#else
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
abort();
#endif
}
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
{
assert(!decrypted);
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX_free(ctx);
#endif
if (tmp)
free(tmp);
}
void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size)
void op_aes_xts_decrypt_t::start(osd_client_t *cl, uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size)
{
assert(!decrypted);
this->cl = cl;
this->start_offset = start_offset;
this->key_chain = chain_size > 1 ? key_chain : 0;
this->chain_size = chain_size > 1 ? chain_size : 0;
this->key_indexes = chain_size > 1 ? key_indexes : NULL;
this->key_chain = key_chain;
this->chain_size = chain_size;
this->key_indexes = key_indexes;
this->key_index_bytes = osd_op_rw_t::chain_info_bytes(chain_size);
assert(chain_size <= 1 || key_indexes != NULL);
this->block_size = block_size;
@@ -204,7 +242,7 @@ void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, void *k
tmp = NULL;
tmp_size = 0;
}
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (chain_size == 1 && key_chain[0] && EVP_DecryptInit_ex(ctx, NULL, NULL, key_chain[0], NULL) != 1)
{
ERR_print_errors_fp(stderr);
@@ -216,7 +254,7 @@ void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, void *k
void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
{
uint8_t *key = NULL;
if (chain_size)
if (chain_size > 1)
{
uint32_t key_index = key_index_bytes == 1
? ((uint8_t*)key_indexes)[offset/block_size]
@@ -227,17 +265,24 @@ void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
: UINT32_MAX));
assert(key_index < chain_size);
key = key_chain[key_index];
if (!key)
{
if (in != out)
memcpy(out, in, block_size);
return;
}
}
#ifdef WITH_OPENSSL
else
{
key = key_chain[0];
}
if (!key)
{
if (in != out)
memcpy(out, in, block_size);
return;
}
uint8_t iv[16] = { 0 };
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1)
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_xts_dec_256(key+32, key, iv, block_size, in, out);
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
#else
if (EVP_DecryptInit_ex(ctx, NULL, NULL, chain_size == 1 ? NULL : key, iv) != 1)
{
ERR_print_errors_fp(stderr);
abort();
@@ -252,6 +297,23 @@ void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
#endif
}
static inline void gcm_dec(osd_client_t *cl, uint8_t *out, uint8_t *in, size_t n)
{
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_dec_256_update(&cl->peer_key_isal, cl->dec_ctx, out, in, n);
assert(!r);
#else
int actual_out;
if (EVP_DecryptUpdate(cl->dec_ctx, out, &actual_out, in, n) != 1)
{
fprintf(stderr, "DecryptUpdate error: ");
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == n);
#endif
}
// out may be NULL, in this case all input is still decrypted to calculate checksums,
// but part of it is skipped and not copied to out
void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
@@ -272,7 +334,10 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
done_out += max_out;
tmp_pos += max_out;
if (tmp_pos >= block_size)
{
decrypted = false;
done_in += 1;
}
}
else if (max_in < block_size - offset%block_size)
{
@@ -296,19 +361,32 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
}
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
if (cl->proto_csum_status == MSGR_CSUM_GCM)
gcm_dec(cl, tmp, tmp, block_size);
decrypt_block(tmp, tmp);
decrypted = true;
if (out)
memcpy(out, tmp, max_out);
tmp_pos = max_out;
done_in += max_in;
done_in += max_in-1;
offset += max_in;
done_out += max_out;
}
else if (!(offset%block_size))
{
// Full block - simplest case
if (out)
if (cl->proto_csum_status == MSGR_CSUM_GCM)
{
if (!tmp)
{
tmp = (uint8_t*)malloc_or_die(block_size);
tmp_size = block_size;
}
gcm_dec(cl, tmp, in, block_size);
if (out)
decrypt_block(tmp, out);
}
else
decrypt_block(in, out);
done_in += block_size;
offset += block_size;
@@ -321,6 +399,8 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
assert(out);
if (cl->proto_csum_status == MSGR_CSUM_GCM)
gcm_dec(cl, tmp, tmp, block_size);
decrypt_block(tmp, out);
done_in += max_in;
offset += max_in;
@@ -335,23 +415,23 @@ void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
void osd_messenger_t::op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
{
if (!cl->encrypt_ctx)
if (!cl->xts_enc_ctx)
{
if (encrypt_ctx_pool.size())
if (encrypt_xts_pool.size())
{
cl->encrypt_ctx = encrypt_ctx_pool.back();
encrypt_ctx_pool.pop_back();
cl->xts_enc_ctx = encrypt_xts_pool.back();
encrypt_xts_pool.pop_back();
}
else
cl->encrypt_ctx = new op_aes_xts_encrypt_t();
cl->xts_enc_ctx = new op_aes_xts_encrypt_t();
assert(cl->write_op->enc->key_chain[0]);
cl->encrypt_ctx->start(cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
cl->xts_enc_ctx->start(cl, cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
}
while (done_enc < enc_len && (done_plain < plain_len || cl->encrypt_ctx->has_buffered()))
while (done_plain < plain_len && done_enc < enc_len)
{
size_t done_in = 0;
size_t done_out = 0;
cl->encrypt_ctx->update(plain+done_plain, plain_len-done_plain, enc_buf+done_enc, enc_len-done_enc, done_in, done_out);
cl->xts_enc_ctx->update(plain+done_plain, plain_len-done_plain, enc_buf+done_enc, enc_len-done_enc, done_in, done_out);
if (cl->write_csum_state && done_out > 0)
XXH3_64bits_update(cl->write_csum_state, enc_buf+done_enc, done_out);
done_enc += done_out;
@@ -368,7 +448,7 @@ void osd_messenger_t::op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf,
size_t done_in = 0;
size_t done_out = 0;
// plain == NULL means skip output
cl->decrypt_ctx->update(enc_buf+done_enc, enc_len-done_enc, plain ? plain+done_plain : NULL, plain_len-done_plain, done_in, done_out);
cl->xts_dec_ctx->update(enc_buf+done_enc, enc_len-done_enc, plain ? plain+done_plain : NULL, plain_len-done_plain, done_in, done_out);
if (cl->read_csum_state && done_in > 0)
XXH3_64bits_update(cl->read_csum_state, enc_buf+done_enc, done_in);
done_enc += done_in;
@@ -380,18 +460,18 @@ void osd_messenger_t::op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf,
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
{
if (!cl->decrypt_ctx)
if (!cl->xts_dec_ctx)
{
if (decrypt_ctx_pool.size())
if (decrypt_xts_pool.size())
{
cl->decrypt_ctx = decrypt_ctx_pool.back();
decrypt_ctx_pool.pop_back();
cl->xts_dec_ctx = decrypt_xts_pool.back();
decrypt_xts_pool.pop_back();
}
else
cl->decrypt_ctx = new op_aes_xts_decrypt_t();
cl->xts_dec_ctx = new op_aes_xts_decrypt_t();
auto & enc = cl->read_op->enc;
assert(cl->read_op->req.hdr.opcode == OSD_OP_READ);
cl->decrypt_ctx->start(enc->key_chain, enc->chain_size,
cl->xts_dec_ctx->start(cl, enc->key_chain, enc->chain_size,
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
cl->read_op->req.rw.offset, enc->bitmap_granularity);
}
@@ -423,7 +503,7 @@ void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
size_t out_len = op->iov.buf[j].iov_len - from_out;
size_t done_in = 0;
size_t done_out = 0;
cl->decrypt_ctx->update(in, in_len, out, out_len, done_in, done_out);
cl->xts_dec_ctx->update(in, in_len, out, out_len, done_in, done_out);
if (done_in >= in_len)
{
i++;
@@ -444,24 +524,138 @@ void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
{
if (cl->decrypt_ctx)
if (cl->xts_dec_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->decrypt_ctx;
if (decrypt_xts_pool.size() > max_cipher_pool_size)
delete cl->xts_dec_ctx;
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
cl->xts_dec_ctx = NULL;
}
}
void osd_messenger_t::op_encrypt_free(osd_client_t* cl)
{
if (cl->encrypt_ctx)
if (cl->xts_enc_ctx)
{
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->encrypt_ctx;
if (encrypt_xts_pool.size() > max_cipher_pool_size)
delete cl->xts_enc_ctx;
else
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
cl->encrypt_ctx = NULL;
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
cl->xts_enc_ctx = NULL;
}
}
bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer)
{
if (!cl->hs_result.shared_secret.size())
{
cl->hs_result = cl->hs->get_result();
if (handshake_hook)
{
handshake_hook(cl);
}
}
std::vector<uint8_t> old_my = cl->my_key, old_peer = cl->peer_key;
// Both keys include AES key and iv + xxhash3 secret
const auto len = AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE;
cl->my_key.resize(len);
cl->peer_key.resize(len);
bool ok = true;
if (update_my || !old_my.size())
{
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
old_my.size() ? old_my.data() : NULL, old_my.size(),
cl->is_incoming ? "server key" : "client key", cl->my_key.data(), len);
#ifdef WITH_ISAL_CRYPTO
if (ok)
isal_aes_gcm_pre_256(cl->my_key.data(), &cl->my_key_isal);
#endif
cl->my_iv_ctr = 0;
}
if (update_peer || !old_peer.size())
{
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
old_peer.size() ? old_peer.data() : NULL, old_peer.size(),
!cl->is_incoming ? "server key" : "client key", cl->peer_key.data(), len);
#ifdef WITH_ISAL_CRYPTO
if (ok)
isal_aes_gcm_pre_256(cl->peer_key.data(), &cl->peer_key_isal);
#endif
cl->peer_iv_ctr = 0;
}
return ok;
}
void osd_messenger_t::init_tls()
{
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
{
if (tls_cert.empty() || tls_key.empty() || osd_tls_ca.empty() || osd_num && client_tls_ca.empty())
{
if (osd_num)
fprintf(stderr, "Vitastor transport encryption requires osd_cert, osd_pkey, osd_ca, client_ca options for OSDs\n");
else
fprintf(stderr, "Vitastor transport encryption requires cert, pkey and osd_ca options\n");
exit(1);
}
else
{
#ifndef __MOCK__
gcm_enabled = true;
hs_ctx = msgr_handshake_ctx_i::create_ctx();
if (!hs_ctx->init(tls_cert, tls_key, osd_tls_ca, client_tls_ca))
{
fprintf(stderr, "Error: %s\n", hs_ctx->get_error().c_str());
exit(1);
}
#endif
}
}
}
void osd_messenger_t::init_tls_client(osd_client_t *cl)
{
if (gcm_enabled)
{
cl->gcm_enabled = true;
cl->hs = hs_ctx->create();
cl->hs->init(cl->is_incoming);
if (cl->hs->out_size())
{
if (cl->write_state == 0)
{
cl->write_state = CL_WRITE_READY;
write_ready_clients.push_back(cl->client_id);
}
}
}
}
void osd_messenger_t::destroy_tls()
{
#ifdef WITH_ISAL_CRYPTO
for (isal_gcm_context_data *ctx: encrypt_gcm_pool)
{
free(ctx);
}
for (isal_gcm_context_data *ctx: decrypt_gcm_pool)
{
free(ctx);
}
#else
for (EVP_CIPHER_CTX *ctx: encrypt_gcm_pool)
{
EVP_CIPHER_CTX_free(ctx);
}
for (EVP_CIPHER_CTX *ctx: decrypt_gcm_pool)
{
EVP_CIPHER_CTX_free(ctx);
}
#endif
if (hs_ctx)
{
delete hs_ctx;
hs_ctx = NULL;
}
}
+12 -9
View File
@@ -3,19 +3,23 @@
#include <stdint.h>
#ifdef WITH_ISAL_CRYPTO
#include <isa-l_crypto/aes_xts.h>
#endif
#include "../util/xxh_x86dispatch.h"
// WITH_OPENSSL is left to possibly support other crypto libraries
#ifdef WITH_OPENSSL
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#endif
struct osd_client_t;
class op_aes_xts_encrypt_t
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX *ctx = NULL;
#endif
osd_client_t *cl = NULL;
uint64_t start_offset = 0;
uint8_t *key = NULL;
size_t offset = 0;
@@ -31,8 +35,7 @@ public:
op_aes_xts_encrypt_t();
~op_aes_xts_encrypt_t();
inline bool has_buffered() { return encrypted; };
void start(uint8_t *key, uint64_t start_offset, size_t block_size);
void start(osd_client_t *cl, uint8_t *key, uint64_t start_offset, size_t block_size);
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
};
@@ -40,9 +43,10 @@ void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
class op_aes_xts_decrypt_t
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX *ctx = NULL;
#endif
osd_client_t *cl = NULL;
uint64_t start_offset = 0;
uint8_t **key_chain = NULL;
size_t chain_size = 0;
@@ -61,8 +65,7 @@ public:
op_aes_xts_decrypt_t();
~op_aes_xts_decrypt_t();
inline bool has_buffered() { return decrypted; };
void start(uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size);
void start(osd_client_t *cl, uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size);
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
};
+810
View File
@@ -0,0 +1,810 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdint.h>
#include <assert.h>
#include <string>
#include <vector>
#include <memory>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
#include <openssl/ec.h>
#include <openssl/kdf.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include "msgr_handshake.h"
#include "malloc_or_die.h"
#include "openssl_util.h"
#include "str_util.h"
#define AES_256_GCM_KEY_SIZE 32
#define AES_256_GCM_IV_SIZE 12
// TLS 1.3-like handshake
// 1. Client->server: EC public key
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
// 3. Client->server: encrypted certificate and digital signature of the handshake
// "vitaECDH" interleaved
#define MSGR_HS_MAGIC 0x4861447443694576l
#define MSGR_HS_MAX_LEN 131072
#define MSGR_HS_SERVER_INIT 0
#define MSGR_HS_CLIENT_INIT 1
#define MSGR_HS_SERVER_REPLY 2
#define MSGR_HS_CLIENT_REPLY 3
#define MSGR_HS_DONE 100
#define MSGR_HS_ERROR 101
struct __attribute__((__packed__)) msgr_handshake_hdr_t
{
uint32_t msg_len;
uint64_t magic;
uint32_t type;
};
class msgr_handshake_ctx_t: public msgr_handshake_ctx_i
{
friend class msgr_handshake_t;
EVP_PKEY_CTX *pctx = NULL;
EVP_PKEY *params = NULL;
X509_STORE *ca = NULL;
X509 *osd_ca = NULL;
X509 *client_ca = NULL;
std::string my_cert_pem;
X509 *my_cert = NULL;
EVP_PKEY *my_pubkey = NULL;
const EVP_MD *md = NULL;
EVP_PKEY *my_privkey = NULL;
EVP_KDF_CTX* kdf_ctx = NULL;
std::string error;
bool on_error(const std::string & prefix);
public:
~msgr_handshake_ctx_t();
msgr_handshake_i* create() override;
bool init(const std::string & pem_cert, const std::string & pem_key,
const std::string & pem_osd_ca, const std::string & pem_client_ca) override;
std::string get_error() override;
bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) override;
};
class msgr_handshake_t: public msgr_handshake_i
{
msgr_handshake_ctx_t *ctx = NULL;
bool is_server = false;
std::vector<uint8_t> full_handshake;
std::vector<uint8_t> in_buf;
uint8_t *out_buf = NULL;
size_t out_buf_size = 0;
std::string error;
int state = 0;
EVP_PKEY *ec_key = NULL;
X509 *peer_cert = NULL;
bool peer_is_osd = false;
std::vector<uint8_t> shared_secret;
std::vector<uint8_t> hs_key, peer_hs_key;
msgr_handshake_hdr_t *cur_hdr = NULL;
uint8_t *cur_buf = NULL;
size_t cur_left = 0;
bool on_error(const std::string & prefix);
bool derive_shared_secret(EVP_PKEY *peer_ec_key);
bool derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len);
bool sign(std::vector<uint8_t> & out);
bool verify(const uint8_t *signature, size_t signature_len);
bool encrypt(const uint8_t* src, size_t len, uint8_t* dest);
bool decrypt(const uint8_t* src, size_t & len, uint8_t* dest);
bool make_client_init();
bool make_server_reply();
bool make_client_reply();
bool verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len);
ssize_t start_msg(uint8_t* src, size_t len, uint32_t expected_type);
bool read_with_len(const uint8_t* & dst, uint32_t & dst_len);
bool handle_client_init();
bool handle_server_reply();
bool handle_client_reply();
bool handle_peer_cert(const uint8_t *key, uint32_t key_len);
void complete();
public:
// Workflow: create -> init -> handle -> get_result/get_error -> destruct
msgr_handshake_t(msgr_handshake_ctx_t *ctx): ctx(ctx) {}
~msgr_handshake_t();
bool init(bool server_mode) override;
ssize_t handle(uint8_t* in_buf, size_t in_size) override;
bool done() override;
uint8_t *get_out() override;
size_t out_size() override;
void eat_out(size_t n) override;
void reset_out() override;
msgr_handshake_result_t get_result() override;
std::string get_error() override;
};
msgr_handshake_ctx_i* msgr_handshake_ctx_i::create_ctx()
{
return new msgr_handshake_ctx_t();
}
msgr_handshake_i* msgr_handshake_ctx_t::create()
{
return new msgr_handshake_t(this);
}
bool msgr_handshake_ctx_t::init(const std::string & pem_cert, const std::string & pem_key,
const std::string & pem_osd_ca, const std::string & pem_client_ca)
{
if (pem_cert.substr(0, 5) == "-----")
my_cert_pem = pem_cert;
else
{
my_cert_pem = read_file(pem_cert);
if (my_cert_pem.empty())
{
error = "Failed to load certificate file";
return false;
}
}
{
BIO *bio = BIO_new_mem_buf(my_cert_pem.data(), my_cert_pem.size());
if (!bio)
return on_error("BIO_new_mem_buf: ");
my_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
BIO_free(bio);
if (!my_cert)
return on_error("Failed to load certificate: ");
}
if (!(my_pubkey = X509_get0_pubkey(my_cert)))
return on_error("X509_get0_pubkey: ");
if (!(md = EVP_get_digestbynid(NID_sha384)))
return on_error("EVP_get_digestbynid SHA384: ");
if (!(my_privkey = openssl_load_key(pem_key)))
return on_error("Failed to load private key: ");
if (!(ca = X509_STORE_new()))
return on_error("X509_STORE_CTX_new: ");
if (!(osd_ca = openssl_load_cert(pem_osd_ca)))
return on_error("Failed to load OSD CA certificate: ");
if (X509_STORE_add_cert(ca, osd_ca) <= 0)
return on_error("X509_STORE_add_cert OSD CA: ");
if (!pem_client_ca.empty() && !(client_ca = openssl_load_cert(pem_client_ca)))
return on_error("Failed to load client CA certificate: ");
if (client_ca && X509_STORE_add_cert(ca, client_ca) <= 0)
return on_error("X509_STORE_add_cert client CA: ");
if (!(pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)))
return on_error("EVP_PKEY_CTX_new_id: ");
if (EVP_PKEY_paramgen_init(pctx) <= 0)
return on_error("EVP_PKEY_paramgen_init: ");
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, /*NID_X9_62_prime256v1*/NID_secp384r1) <= 0)
return on_error("EVP_PKEY_CTX_set_ec_paramgen_curve_nid: ");
if (EVP_PKEY_paramgen(pctx, &params) <= 0)
return on_error("EVP_PKEY_paramgen: ");
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
if (!kdf)
return on_error("EVP_KDF_fetch: ");
kdf_ctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);
return true;
}
std::string msgr_handshake_ctx_t::get_error()
{
return error;
}
msgr_handshake_ctx_t::~msgr_handshake_ctx_t()
{
if (pctx)
EVP_PKEY_CTX_free(pctx);
if (params)
EVP_PKEY_free(params);
if (ca)
X509_STORE_free(ca);
if (osd_ca)
X509_free(osd_ca);
if (client_ca)
X509_free(client_ca);
my_pubkey = NULL;
if (my_cert)
X509_free(my_cert);
if (my_privkey)
EVP_PKEY_free(my_privkey);
if (kdf_ctx)
EVP_KDF_CTX_free(kdf_ctx);
}
bool msgr_handshake_ctx_t::on_error(const std::string & prefix)
{
error = prefix+ERR_error_string(ERR_get_error(), NULL);
return false;
}
bool msgr_handshake_ctx_t::derive_kdf(const uint8_t* insecret, size_t insecret_len,
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize)
{
OSSL_PARAM params[5];
int n = 0;
params[n++] = OSSL_PARAM_construct_utf8_string("digest", (char*)"sha384", (size_t)7);
params[n++] = OSSL_PARAM_construct_octet_string("key", (void*)insecret, insecret_len);
params[n++] = OSSL_PARAM_construct_octet_string("info", (void*)label, strlen(label)+1);
params[n++] = OSSL_PARAM_construct_octet_string("salt", (salt ? (void*)salt : (void*)""), salt_len);
params[n++] = OSSL_PARAM_construct_end();
assert(n <= sizeof(params)/sizeof(OSSL_PARAM));
if (EVP_KDF_CTX_set_params(kdf_ctx, params) <= 0)
return false;
if (EVP_KDF_derive(kdf_ctx, outsecret, outsize, NULL) <= 0)
return false;
return true;
}
msgr_handshake_t::~msgr_handshake_t()
{
if (out_buf)
free(out_buf);
if (peer_cert)
X509_free(peer_cert);
if (ec_key)
EVP_PKEY_free(ec_key);
}
bool msgr_handshake_t::on_error(const std::string & prefix)
{
error = prefix+ERR_error_string(ERR_get_error(), NULL);
state = MSGR_HS_ERROR;
return false;
}
bool msgr_handshake_t::init(bool server_mode)
{
this->ctx = ctx;
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> kctx(EVP_PKEY_CTX_new(ctx->params, NULL), EVP_PKEY_CTX_free);
if (!kctx)
return on_error("EVP_PKEY_CTX_new with EC params: ");
if (EVP_PKEY_keygen_init(kctx.get()) <= 0)
return on_error("EVP_PKEY_keygen_init: ");
if (EVP_PKEY_keygen(kctx.get(), &ec_key) <= 0)
return on_error("EVP_PKEY_keygen: ");
if (!server_mode)
{
// Send initial message - only the EC public key
if (!make_client_init())
return false;
}
this->is_server = server_mode;
this->state = server_mode ? MSGR_HS_SERVER_INIT : MSGR_HS_CLIENT_INIT;
return true;
}
static void copy_to(std::vector<uint8_t> & buf, const void* src, uint32_t len)
{
size_t old_size = buf.size();
buf.resize(buf.size() + len);
memcpy(buf.data() + old_size, src, len);
}
static void copy_to_raw(uint8_t* & buf, const void* src, size_t len)
{
memcpy(buf, src, len);
buf += len;
}
static void copy_to_with_len(std::vector<uint8_t> & buf, const void* src, uint32_t len)
{
copy_to(buf, &len, sizeof(len));
copy_to(buf, src, len);
}
bool msgr_handshake_t::derive_shared_secret(EVP_PKEY *peer_ec_key)
{
EVP_PKEY_CTX *dh_ctx = NULL;
if (!(dh_ctx = EVP_PKEY_CTX_new(ec_key, NULL)))
return on_error("EVP_PKEY_CTX_new for ECDH: ");
if (!EVP_PKEY_derive_init(dh_ctx))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive_init: ");
}
if (!EVP_PKEY_derive_set_peer(dh_ctx, peer_ec_key))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive_set_peer: ");
}
size_t len = 0;
if (!EVP_PKEY_derive(dh_ctx, NULL, &len))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive get length: ");
}
shared_secret.resize(len);
assert(len == 48);
if (!EVP_PKEY_derive(dh_ctx, shared_secret.data(), &len))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive: ");
}
assert(len == shared_secret.size());
shared_secret.resize(len);
EVP_PKEY_CTX_free(dh_ctx);
return true;
}
bool msgr_handshake_t::derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len)
{
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> peer_ec_key(EVP_PKEY_new(), EVP_PKEY_free);
if (!peer_ec_key)
return on_error("EVP_PKEY_new: ");
if (EVP_PKEY_copy_parameters(peer_ec_key.get(), ec_key) <= 0)
return on_error("EVP_PKEY_copy_parameters: ");
if (EVP_PKEY_set1_encoded_public_key(peer_ec_key.get(), encoded_peer_key, encoded_key_len) <= 0)
return on_error("Invalid handshake peer key: ");
if (!derive_shared_secret(peer_ec_key.get()))
return false;
hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
peer_hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
NULL, 0, (state == MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
hs_key.data(), hs_key.size()))
return on_error("derive_kdf: ");
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
NULL, 0, (state != MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
peer_hs_key.data(), peer_hs_key.size()))
return on_error("derive_kdf: ");
return true;
}
bool msgr_handshake_t::sign(std::vector<uint8_t> & out)
{
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
if (!md_ctx)
return on_error("EVP_MD_CTX_create: ");
if (EVP_DigestSignInit(md_ctx.get(), NULL, ctx->md, NULL, ctx->my_privkey) <= 0)
return on_error("EVP_DigestSignInit: ");
if (EVP_DigestSignUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
return on_error("EVP_DigestSignUpdate: ");
size_t siglen = 0;
if (EVP_DigestSignFinal(md_ctx.get(), NULL, &siglen) <= 0)
return on_error("EVP_DigestSignFinal get length: ");
size_t oldsize = out.size();
out.resize(oldsize + siglen);
if (EVP_DigestSignFinal(md_ctx.get(), out.data() + oldsize, &siglen) <= 0)
return on_error("EVP_DigestSignFinal: ");
out.resize(oldsize + siglen);
return true;
}
bool msgr_handshake_t::verify(const uint8_t *signature, size_t signature_len)
{
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
if (!md_ctx)
return on_error("EVP_MD_CTX_create: ");
if (EVP_DigestVerifyInit(md_ctx.get(), NULL, ctx->md, NULL, X509_get0_pubkey(peer_cert)) <= 0)
return on_error("EVP_DigestVerifyInit: ");
if (EVP_DigestVerifyUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
return on_error("EVP_DigestVerifyUpdate: ");
if (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) <= 0)
return false;
return true;
}
bool msgr_handshake_t::encrypt(const uint8_t* src, size_t len, uint8_t* dest)
{
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> enc_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
if (!ctx)
return on_error("EVP_CIPHER_CTX_new: ");
if (EVP_EncryptInit_ex(enc_ctx.get(), EVP_aes_256_gcm(), NULL, hs_key.data(), hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
return on_error("EVP_EncryptInit AES-256-GCM: ");
int actual_out;
if (EVP_EncryptUpdate(enc_ctx.get(), dest, &actual_out, src, len) <= 0)
return on_error("EVP_EncryptUpdate: ");
assert(actual_out == len);
if (EVP_EncryptFinal_ex(enc_ctx.get(), NULL, &actual_out) <= 0)
return on_error("EVP_EncryptFinal: ");
if (EVP_CIPHER_CTX_ctrl(enc_ctx.get(), EVP_CTRL_GCM_GET_TAG, 16, dest+len) <= 0)
return on_error("EVP_CTRL_GCM_GET_TAG: ");
(*(uint64_t*)(hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
return true;
}
bool msgr_handshake_t::decrypt(const uint8_t *src, size_t & len, uint8_t* dest)
{
if (len <= 16) // only tag?!
{
len = 0;
error = "Handshake decryption failed";
state = MSGR_HS_ERROR;
return false;
}
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> dec_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
if (!ctx)
return on_error("EVP_CIPHER_CTX_new: ");
if (EVP_DecryptInit_ex(dec_ctx.get(), EVP_aes_256_gcm(), NULL, peer_hs_key.data(), peer_hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
return on_error("EVP_DecryptInit AES-256-GCM: ");
int actual_out;
len -= 16;
if (EVP_DecryptUpdate(dec_ctx.get(), dest, &actual_out, src, len) <= 0)
return on_error("EVP_DecryptUpdate: ");
assert(actual_out == len);
if (EVP_CIPHER_CTX_ctrl(dec_ctx.get(), EVP_CTRL_GCM_SET_TAG, 16, (void*)(src+len)) <= 0)
return on_error("EVP_CTRL_GCM_SET_TAG: ");
if (EVP_DecryptFinal_ex(dec_ctx.get(), NULL, &actual_out) <= 0)
{
error = "Handshake decryption failed";
state = MSGR_HS_ERROR;
return false;
}
(*(uint64_t*)(peer_hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
return true;
}
bool msgr_handshake_t::make_client_init()
{
uint8_t *key = NULL;
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
if (!key_len)
return on_error("EVP_PKEY_get1_encoded_public_key: ");
const size_t old_out_size = out_buf_size;
out_buf_size += key_len + sizeof(msgr_handshake_hdr_t);
out_buf = (uint8_t*)realloc_or_die(out_buf, out_buf_size);
uint8_t *buf = out_buf + old_out_size;
msgr_handshake_hdr_t *hdr = (msgr_handshake_hdr_t *)buf;
hdr->msg_len = key_len + sizeof(msgr_handshake_hdr_t);
hdr->magic = MSGR_HS_MAGIC;
hdr->type = MSGR_HS_CLIENT_INIT;
memcpy(buf + sizeof(msgr_handshake_hdr_t), key, key_len);
copy_to(full_handshake, &hdr->type, sizeof(hdr->type));
copy_to_with_len(full_handshake, key, key_len);
OPENSSL_free(key);
return true;
}
bool msgr_handshake_t::make_server_reply()
{
uint8_t *key = NULL;
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
if (!key_len)
return on_error("EVP_PKEY_get1_encoded_public_key: ");
// Append type, key and raw certificate to signed data and sign it
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_SERVER_REPLY };
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
copy_to_with_len(full_handshake, key, key_len);
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
std::vector<uint8_t> signature;
if (!sign(signature))
{
OPENSSL_free(key);
return false;
}
// Encrypt certificate and signature
std::vector<uint8_t> encrypt_data;
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
copy_to_with_len(encrypt_data, signature.data(), signature.size());
encrypt_data.resize(encrypt_data.size()+16);
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
{
OPENSSL_free(key);
return false;
}
// Construct message
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + 4 + key_len + encrypt_data.size();
out_buf = (uint8_t*)realloc_or_die(out_buf, (out_buf_size += hdr.msg_len));
uint8_t *cur = out_buf + out_buf_size - hdr.msg_len;
copy_to_raw(cur, &hdr, sizeof(hdr));
copy_to_raw(cur, &key_len, 4);
copy_to_raw(cur, key, key_len);
copy_to_raw(cur, encrypt_data.data(), encrypt_data.size());
OPENSSL_free(key);
return true;
}
bool msgr_handshake_t::make_client_reply()
{
// Append type and raw certificate to signed data and sign it
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_CLIENT_REPLY };
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
std::vector<uint8_t> signature;
if (!sign(signature))
return false;
// Encrypt certificate and signature
std::vector<uint8_t> encrypt_data;
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
copy_to_with_len(encrypt_data, signature.data(), signature.size());
encrypt_data.resize(encrypt_data.size()+16);
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
return false;
// Construct message
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + encrypt_data.size();
out_buf = (uint8_t*)realloc_or_die(out_buf, (out_buf_size += hdr.msg_len));
uint8_t *cur = out_buf + out_buf_size - hdr.msg_len;
copy_to_raw(cur, &hdr, sizeof(hdr));
copy_to_raw(cur, encrypt_data.data(), encrypt_data.size());
return true;
}
bool msgr_handshake_t::verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len)
{
BIO *bio = BIO_new_mem_buf(peer_cert_pem, peer_cert_len);
if (!bio)
return on_error("BIO_new_mem_buf: ");
peer_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
BIO_free(bio);
if (!peer_cert)
{
error = "Invalid peer certificate";
state = MSGR_HS_ERROR;
return false;
}
std::unique_ptr<X509_STORE_CTX, decltype(&X509_STORE_CTX_free)> ca_ctx(X509_STORE_CTX_new(), X509_STORE_CTX_free);
if (!ca_ctx)
return on_error("X509_STORE_CTX_new: ");
if (X509_STORE_CTX_init(ca_ctx.get(), ctx->ca, peer_cert, NULL) <= 0)
return on_error("X509_STORE_CTX_init: ");
// Maybe use X509_VERIFY_PARAM_set_auth_level(X509_STORE_CTX_get0_param(ca_ctx.get()), 2) ?
X509_STORE_CTX_set_default(ca_ctx.get(), is_server ? "ssl_client" : "ssl_server");
if (X509_verify_cert(ca_ctx.get()) <= 0)
{
error = "Peer certificate verification failed: ";
error += X509_verify_cert_error_string(X509_STORE_CTX_get_error(ca_ctx.get()));
state = MSGR_HS_ERROR;
return false;
}
peer_is_osd = (X509_verify(peer_cert, X509_get0_pubkey(ctx->osd_ca)) > 0);
if (!is_server && !peer_is_osd)
{
error = "Peer is not an OSD";
state = MSGR_HS_ERROR;
return false;
}
return true;
}
ssize_t msgr_handshake_t::start_msg(uint8_t* src, size_t len, uint32_t expected_type)
{
size_t orig_len = len;
size_t to_buffer = (len < sizeof(msgr_handshake_hdr_t)-in_buf.size()
? len : sizeof(msgr_handshake_hdr_t)-in_buf.size());
in_buf.insert(in_buf.end(), src, src+to_buffer);
len -= to_buffer;
src += to_buffer;
if (in_buf.size() < sizeof(msgr_handshake_hdr_t))
return 0;
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
if (cur_hdr->magic != MSGR_HS_MAGIC ||
cur_hdr->type != expected_type ||
cur_hdr->msg_len <= sizeof(msgr_handshake_hdr_t) ||
cur_hdr->msg_len >= MSGR_HS_MAX_LEN)
{
error = "Invalid handshake packet magic, type or size";
state = MSGR_HS_ERROR;
return -1;
}
to_buffer = (len < cur_hdr->msg_len-in_buf.size()
? len : cur_hdr->msg_len-in_buf.size());
in_buf.insert(in_buf.end(), src, src+to_buffer);
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
len -= to_buffer;
src += to_buffer;
if (in_buf.size() < cur_hdr->msg_len)
return 0;
cur_left = cur_hdr->msg_len - sizeof(msgr_handshake_hdr_t);
cur_buf = in_buf.data() + sizeof(msgr_handshake_hdr_t);
return orig_len - len;
}
bool msgr_handshake_t::read_with_len(const uint8_t* & dst, uint32_t & dst_len)
{
if (cur_left < 4)
{
error = "Handshake packet too short";
state = MSGR_HS_ERROR;
return false;
}
dst_len = *(uint32_t*)cur_buf;
cur_buf += 4;
cur_left -= 4;
if (cur_left < dst_len)
{
error = "Handshake packet too short";
state = MSGR_HS_ERROR;
return false;
}
dst = cur_buf;
cur_buf += dst_len;
cur_left -= dst_len;
return true;
}
bool msgr_handshake_t::handle_client_init()
{
// Derive shared secret and handshake keys
if (!derive_hs_keys(cur_buf, cur_left))
return false;
// Add type and key to full_handshake
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
copy_to_with_len(full_handshake, cur_buf, cur_left);
in_buf.clear();
return true;
}
// Decrypt and check peer certificate
bool msgr_handshake_t::handle_peer_cert(const uint8_t *key, uint32_t key_len)
{
if (!decrypt(cur_buf, cur_left, cur_buf))
return false;
const uint8_t *peer_cert_pem = NULL;
uint32_t peer_cert_len = 0;
if (!read_with_len(peer_cert_pem, peer_cert_len))
return false;
// Parse and verify certificate
if (!verify_peer(peer_cert_pem, peer_cert_len))
return false;
// Verify signature
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
if (key)
copy_to_with_len(full_handshake, key, key_len);
copy_to_with_len(full_handshake, peer_cert_pem, peer_cert_len);
const uint8_t *signature = NULL;
uint32_t signature_len = 0;
if (!read_with_len(signature, signature_len))
return false;
if (!verify(signature, signature_len))
return false;
return true;
}
bool msgr_handshake_t::handle_server_reply()
{
// Derive shared secret and handshake keys
const uint8_t *key = NULL;
uint32_t key_len = 0;
if (!read_with_len(key, key_len))
return false;
if (!derive_hs_keys(key, key_len))
return false;
// Decrypt and check peer certificate
if (!handle_peer_cert(key, key_len))
return false;
in_buf.clear();
return true;
}
bool msgr_handshake_t::handle_client_reply()
{
// Decrypt and check peer certificate
if (!handle_peer_cert(NULL, 0))
return false;
in_buf.clear();
return true;
}
void msgr_handshake_t::complete()
{
state = MSGR_HS_DONE;
hs_key.clear();
peer_hs_key.clear();
full_handshake.clear();
}
ssize_t msgr_handshake_t::handle(uint8_t* in_buf, size_t in_size)
{
if (state == MSGR_HS_SERVER_INIT)
{
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_INIT);
if (r < 0)
return r;
if (r == 0)
return in_size;
if (!handle_client_init())
return -1;
// Send encrypted & signed response
if (!make_server_reply())
return -1;
state = MSGR_HS_SERVER_REPLY;
return r;
}
else if (state == MSGR_HS_CLIENT_INIT)
{
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_SERVER_REPLY);
if (r < 0)
return r;
if (r == 0)
return in_size;
if (!handle_server_reply())
return -1;
// Verification passed, send certificate to the server
if (!make_client_reply())
return -1;
// Finished!
complete();
return r;
}
else if (state == MSGR_HS_SERVER_REPLY)
{
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_REPLY);
if (r < 0)
return r;
if (r == 0)
return in_size;
if (!handle_client_reply())
return -1;
// Verification passed
// Finished!
complete();
return r;
}
else if (state == MSGR_HS_DONE)
{
return 0;
}
else if (state != MSGR_HS_ERROR)
{
error = "Unexpected handshake state: "+std::to_string(state);
}
return -1;
}
bool msgr_handshake_t::done()
{
return (state == MSGR_HS_DONE);
}
uint8_t *msgr_handshake_t::get_out()
{
return out_buf;
}
size_t msgr_handshake_t::out_size()
{
return out_buf_size;
}
void msgr_handshake_t::eat_out(size_t n)
{
if (n >= out_buf_size)
{
free(out_buf);
out_buf = NULL;
out_buf_size = 0;
}
else
{
memmove(out_buf, out_buf + n, out_buf_size - n);
out_buf_size -= n;
}
}
void msgr_handshake_t::reset_out()
{
out_buf = NULL;
out_buf_size = 0;
}
msgr_handshake_result_t msgr_handshake_t::get_result()
{
if (state != MSGR_HS_DONE)
return msgr_handshake_result_t{};
X509_up_ref(peer_cert);
return msgr_handshake_result_t{
.peer_cert = peer_cert,
.peer_is_osd = peer_is_osd,
.shared_secret = shared_secret,
};
}
std::string msgr_handshake_t::get_error()
{
return error;
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#pragma once
#include <stdint.h>
#include <string>
#include <vector>
#include <openssl/types.h>
#define AES_256_GCM_KEY_SIZE 32
#define AES_256_GCM_IV_SIZE 12
#define AES_256_GCM_MAX_IV_CTR ((uint64_t)1 << 32)
// TLS 1.3-like handshake
// 1. Client->server: EC public key
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
// 3. Client->server: encrypted certificate and digital signature of the handshake
struct msgr_handshake_result_t
{
X509 *peer_cert = NULL;
bool peer_is_osd = false;
std::vector<uint8_t> shared_secret;
};
class msgr_handshake_i
{
public:
// Workflow: create -> init -> handle_msg -> get_result/get_error -> destruct
virtual ~msgr_handshake_i() = default;
virtual bool init(bool server_mode) = 0;
virtual ssize_t handle(uint8_t* in_buf, size_t in_size) = 0;
virtual bool done() = 0;
virtual uint8_t *get_out() = 0;
virtual size_t out_size() = 0;
virtual void eat_out(size_t n) = 0;
virtual void reset_out() = 0;
virtual msgr_handshake_result_t get_result() = 0;
virtual std::string get_error() = 0;
};
class msgr_handshake_ctx_i
{
public:
static msgr_handshake_ctx_i* create_ctx();
virtual ~msgr_handshake_ctx_i() = default;
virtual msgr_handshake_i* create() = 0;
virtual bool init(const std::string & pem_cert, const std::string & pem_key,
const std::string & pem_osd_ca, const std::string & pem_client_ca) = 0;
virtual std::string get_error() = 0;
virtual bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) = 0;
};
+129
View File
@@ -0,0 +1,129 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdexcept>
#include <sys/poll.h>
#include <unistd.h>
#include "messenger.h"
#include "msgr_iothread.h"
msgr_iothread_t::msgr_iothread_t():
ring(RINGLOOP_DEFAULT_SIZE, true),
thread(&msgr_iothread_t::run, this)
{
eventfd = ring.register_eventfd();
if (eventfd < 0)
{
throw std::runtime_error(std::string("failed to register eventfd: ") + strerror(-eventfd));
}
}
msgr_iothread_t::~msgr_iothread_t()
{
stop();
}
void msgr_iothread_t::add_sqe(io_uring_sqe & sqe)
{
mu.lock();
queue.push_back((iothread_sqe_t){ .sqe = sqe, .data = std::move(*(ring_data_t*)sqe.user_data) });
if (queue.size() == 1)
{
cond.notify_all();
}
mu.unlock();
}
void msgr_iothread_t::stop()
{
mu.lock();
if (stopped)
{
mu.unlock();
return;
}
stopped = true;
if (outer_loop_data)
{
outer_loop_data->callback = [](ring_data_t*){};
}
cond.notify_all();
close(eventfd);
mu.unlock();
thread.join();
}
void msgr_iothread_t::add_to_ringloop(ring_loop_i *outer_loop)
{
assert(!this->outer_loop || this->outer_loop == outer_loop);
io_uring_sqe *sqe = outer_loop->get_sqe();
assert(sqe != NULL);
this->outer_loop = outer_loop;
this->outer_loop_data = ((ring_data_t*)sqe->user_data);
io_uring_prep_poll_add(sqe, eventfd, POLLIN);
outer_loop_data->callback = [this](ring_data_t *data)
{
if (data->res < 0)
{
throw std::runtime_error(std::string("eventfd poll failed: ") + strerror(-data->res));
}
outer_loop_data = NULL;
if (stopped)
{
return;
}
add_to_ringloop(this->outer_loop);
ring.loop();
};
}
void msgr_iothread_t::run()
{
while (true)
{
{
std::unique_lock<std::mutex> lk(mu);
while (!stopped && !queue.size())
cond.wait(lk);
if (stopped)
return;
int i = 0;
for (; i < queue.size(); i++)
{
io_uring_sqe *sqe = ring.get_sqe();
if (!sqe)
break;
ring_data_t *data = ((ring_data_t*)sqe->user_data);
*data = std::move(queue[i].data);
*sqe = queue[i].sqe;
sqe->user_data = (uint64_t)data;
}
queue.erase(queue.begin(), queue.begin()+i);
}
// We only want to offload sendmsg/recvmsg. Callbacks will be called in main thread
ring.submit();
}
}
void osd_messenger_t::init_iothreads()
{
for (int i = 0; i < iothread_count; i++)
{
auto iot = new msgr_iothread_t();
iothreads.push_back(iot);
iot->add_to_ringloop(ringloop);
}
}
void osd_messenger_t::destroy_iothreads()
{
if (iothreads.size())
{
for (auto iot: iothreads)
{
delete iot;
}
iothreads.clear();
}
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <mutex>
#include <condition_variable>
#include <thread>
#include "ringloop.h"
struct iothread_sqe_t
{
io_uring_sqe sqe;
ring_data_t data;
};
class msgr_iothread_t
{
protected:
ring_loop_t ring;
ring_loop_i *outer_loop = NULL;
ring_data_t *outer_loop_data = NULL;
int eventfd = -1;
bool stopped = false;
std::mutex mu;
std::condition_variable cond;
std::vector<iothread_sqe_t> queue;
std::thread thread;
void run();
public:
msgr_iothread_t();
~msgr_iothread_t();
void add_sqe(io_uring_sqe & sqe);
void stop();
void add_to_ringloop(ring_loop_i *outer_loop);
};
+1 -8
View File
@@ -8,6 +8,7 @@
osd_op_t::~osd_op_t()
{
assert(!bs_op);
assert(!op_data);
if (bitmap_buf)
{
free(bitmap_buf);
@@ -22,14 +23,6 @@ osd_op_t::~osd_op_t()
// So we don't reuse it, but free it every time
free(buf);
}
if (enc_buf)
{
free(enc_buf);
}
if (op_data)
{
free(op_data);
}
}
bool osd_op_t::is_recovery_related()
+2 -2
View File
@@ -174,6 +174,7 @@ struct __attribute__((visibility("default"))) osd_op_t
timespec tv_begin = { 0 }, tv_end = { 0 };
uint64_t op_type = OSD_OP_IN;
uint64_t client_id = 0;
osd_num_t osd_num = 0;
osd_any_op_t req;
osd_any_reply_t reply;
blockstore_op_t *bs_op = NULL;
@@ -181,11 +182,10 @@ struct __attribute__((visibility("default"))) osd_op_t
// bitmap, bitmap_len, bmp_data are only meaningful for reads
void *bitmap = NULL;
unsigned bitmap_len = 0;
unsigned bmp_data = 0;
size_t bmp_data = 0;
uint8_t *bitmap_buf = NULL;
void *rmw_buf = NULL;
std::shared_ptr<osd_op_enc_t> enc;
uint8_t *enc_buf = NULL;
uint64_t csum = 0; // network layer checksum
osd_primary_op_data_t* op_data = NULL;
std::function<void(osd_op_t*)> callback;
+22 -33
View File
@@ -590,36 +590,9 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
while (!rc->send_out_full && copied > 0 && rc->cur_send < rc->max_send)
{
dst = (uint8_t*)rc->send_out.buf + rc->send_out_pos;
if (rc->send_out_pos >= rc->send_done_pos)
{
dst_len = rc->send_out_size-rc->send_out_pos;
if (dst_len < 4096)
{
// free end of the buffer is too small, skip
rc->send_out_pos = 0;
if (rc->send_out_pos >= rc->send_done_pos)
rc->send_out_full = true;
if (!rc->send_sizes.size())
{
rc->send_done_pos += dst_len;
rc->send_out_full = false;
if (rc->send_done_pos == rc->send_out_size)
rc->send_done_pos = 0;
}
else
rc->send_sizes.back() += dst_len;
continue;
}
}
else
{
dst_len = rc->send_done_pos-rc->send_out_pos;
if (dst_len < 4096)
{
// too small buffer, stop
break;
}
}
dst_len = (rc->send_out_pos >= rc->send_done_pos
? rc->send_out_size-rc->send_out_pos
: rc->send_done_pos-rc->send_out_pos);
if (dst_len > rc->max_msg)
dst_len = rc->max_msg;
copied = copy_ops_to(cl, dst, dst_len);
@@ -634,7 +607,7 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
if (rc->send_out_pos == rc->send_out_size)
rc->send_out_pos = 0;
assert(rc->send_out_pos < rc->send_out_size);
if (rc->send_out_pos >= rc->send_done_pos)
if (rc->send_out_pos == rc->send_done_pos)
rc->send_out_full = true;
ibv_sge sge = {
.addr = (uintptr_t)dst,
@@ -759,9 +732,12 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
if (rc->send_done_pos == rc->send_out_size)
rc->send_done_pos = 0;
assert(rc->send_done_pos < rc->send_out_size);
while (cl->send_free_ops.front())
while (osd_op_t *op = cl->send_free_ops.front())
{
delete cl->send_free_ops.front();
if (!((size_t)op & 7))
delete op;
else
free((void*)((size_t)op & ~(size_t)7));
cl->send_free_ops.pop_front();
}
cl->send_free_ops.pop_front();
@@ -775,3 +751,16 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
}
} while (event_count > 0);
}
void osd_messenger_t::destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn)
{
if (rdma_conn->cmid)
{
auto rdma_it = rdmacm_connections.find(rdma_conn->cmid);
if (rdma_it != rdmacm_connections.end() && rdma_it->second->rdma_conn == rdma_conn)
{
rdmacm_connections.erase(rdma_it);
}
}
delete rdma_conn;
}
+4
View File
@@ -19,6 +19,7 @@ struct rdmacm_connecting_t
int tcp_port = 0;
int timeout_ms = 0;
int timeout_id = -1;
bool is_incoming = false;
msgr_rdma_context_t *rdma_context = NULL;
};
@@ -292,6 +293,7 @@ void osd_messenger_t::rdmacm_accept(rdma_cm_event *ev)
conn->client_id = next_client_id++;
conn->parsed_addr = *(sockaddr_storage*)rdma_get_peer_addr(ev->id);
conn->rdma_context = rdma_context;
conn->is_incoming = true;
rdmacm_set_conn_timeout(conn);
rdmacm_connecting[ev->id] = conn;
fprintf(stderr, "[OSD %ju] new client %ju: connection from %s via RDMA-CM\n", this->osd_num, conn->client_id,
@@ -492,11 +494,13 @@ void osd_messenger_t::rdmacm_established(rdma_cm_event *ev)
cl->peer_addr = conn->parsed_addr;
cl->peer_port = conn->rdmacm_port;
cl->client_id = conn->client_id;
cl->is_incoming = conn->is_incoming;
cl->peer_state = PEER_RDMA;
cl->connect_timeout_id = -1;
cl->osd_num = peer_osd;
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
cl->rdma_conn = rc;
init_tls_client(cl);
clients[conn->client_id] = cl;
if (conn->timeout_id >= 0)
{
+251 -318
View File
@@ -4,15 +4,13 @@
#define _XOPEN_SOURCE
#include <limits.h>
#include "messenger.h"
#include "msgr_iothread.h"
#include "openssl_util.h"
#ifdef WITH_OPENSSL
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#endif
#define RDR_TLS 1
#define RDR_GCM 1
#define RDR_XTS 2
#define RDR_NO_CSUM 4
@@ -91,183 +89,6 @@ public:
}
};
class ssl_op_reader_t: public msgr_op_reader_t
{
osd_messenger_t* msgr;
osd_client_t* cl;
size_t from;
uint8_t *curbuf;
size_t bufsize;
size_t done;
public:
ssl_op_reader_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t *curbuf, size_t bufsize):
msgr(msgr), cl(cl), from(cl->read_op_pos), curbuf(curbuf), bufsize(bufsize), done(0)
{
}
void reset()
{
from = cl->read_op_pos;
}
void buffer_encrypted()
{
if (cl->ssl_read_header_size < sizeof(msgr_tls_record_hdr_t))
{
size_t h = bufsize-done;
if (bufsize-done <= sizeof(msgr_tls_record_hdr_t)-cl->ssl_read_header_size)
{
// Less than record header or just record header
memcpy(((uint8_t*)&cl->ssl_read_record) + cl->ssl_read_header_size, curbuf+done, h);
cl->ssl_read_header_size += h;
if (cl->ssl_read_header_size == sizeof(msgr_tls_record_hdr_t))
cl->ssl_read_record.size = ntohs(cl->ssl_read_record.size);
int r = BIO_write(cl->write_to_ssl, curbuf+done, h);
assert(r == h);
done += h;
return;
}
// Record header and at least some data - copy both to BIO in a one BIO_write() call
h = sizeof(msgr_tls_record_hdr_t)-cl->ssl_read_header_size;
memcpy(((uint8_t*)&cl->ssl_read_record) + cl->ssl_read_header_size, curbuf+done, h);
cl->ssl_read_header_size = sizeof(msgr_tls_record_hdr_t);
cl->ssl_read_record.size = ntohs(cl->ssl_read_record.size);
size_t n = h + cl->ssl_read_record.size;
if (n > bufsize-done)
n = bufsize-done;
int r = BIO_write(cl->write_to_ssl, curbuf+done, n);
assert(r == n);
done += n;
cl->ssl_read_record.size -= (n - h);
if (!cl->ssl_read_record.size)
cl->ssl_read_header_size = 0;
return;
}
// Continued TLS data - buffer it to BIO
size_t n = cl->ssl_read_record.size;
if (n > bufsize-done)
n = bufsize-done;
int r = BIO_write(cl->write_to_ssl, curbuf+done, n);
assert(r == n);
done += n;
cl->ssl_read_record.size -= n;
if (!cl->ssl_read_record.size)
cl->ssl_read_header_size = 0;
}
bool read(uint8_t *dst, size_t dst_len, int flags) override
{
if (from >= dst_len)
{
// Skip
from -= dst_len;
return true;
}
if (done >= bufsize)
return false;
size_t n = dst_len-from;
if (!(flags & RDR_TLS) || !cl->ssl_cli)
{
if (n > bufsize-done)
n = bufsize-done;
if (flags & RDR_XTS)
{
msgr->op_decrypted_copy_buf(cl, curbuf, bufsize, dst, dst_len, from, done);
n = 0;
}
else
{
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
{
// data may be skipped if dst == NULL but checksum is still calculated
XXH3_64bits_update(cl->read_csum_state, curbuf+done, n);
}
// Here, dst == NULL is allowed
if (dst != NULL)
memcpy(dst+from, curbuf+done, n);
done += n;
}
cl->read_op_pos += n;
from += n;
if (from < dst_len)
{
return false;
}
}
else
{
// Here, dst == NULL is not allowed
assert(dst != NULL);
buffer_again:
buffer_encrypted();
if (!cl->ssl_handshake_done)
{
if (!msgr->ssl_do_handshake(cl))
return false;
if (cl->write_state == 0)
{
// SSL_ERROR_WANT_WRITE is absolutely non-informative with memory BIO, it basically never happens
// So we have to check memory BIO for outstanding data
char *bio_buf = NULL;
size_t bio_sz = BIO_get_mem_data(cl->read_from_ssl, &bio_buf);
if (bio_sz > 0)
{
cl->write_state = CL_WRITE_READY;
msgr->write_ready_clients.push_back(cl->client_id);
}
}
}
int ok = SSL_read_ex(cl->ssl_cli, dst+from, n, &n);
if (!ok)
{
ok = SSL_get_error(cl->ssl_cli, ok);
if (ok == SSL_ERROR_WANT_READ)
{
if (done < bufsize)
goto buffer_again;
}
else if (ok == SSL_ERROR_ZERO_RETURN)
{
fprintf(stderr, "Client %ju TLS disconnected\n", cl->client_id);
cl->io_error = true;
}
else if (ok != 0 && ok != SSL_ERROR_WANT_WRITE)
{
fprintf(stderr, "Client %ju TLS read error: %s. Disconnecting client\n", cl->client_id, ERR_error_string(ERR_get_error(), NULL));
cl->io_error = true;
}
return false;
}
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
{
XXH3_64bits_update(cl->read_csum_state, dst+from, n);
}
cl->read_op_pos += n;
from += n;
if (from < dst_len)
{
if (done < bufsize)
goto buffer_again;
return false;
}
}
from = 0;
return true;
}
bool finish() override
{
return true;
}
size_t get_done()
{
return done;
}
};
class gcm_op_reader_t: public msgr_op_reader_t
{
osd_messenger_t* msgr;
@@ -287,6 +108,55 @@ public:
void reset()
{
from = cl->read_op_pos;
if (!cl->dec_ctx)
{
if (msgr->decrypt_gcm_pool.size())
{
cl->dec_ctx = msgr->decrypt_gcm_pool.back();
msgr->decrypt_gcm_pool.pop_back();
}
else
{
#ifdef WITH_ISAL_CRYPTO
cl->dec_ctx = (isal_gcm_context_data*)malloc_or_die(sizeof(isal_gcm_context_data));
#else
cl->dec_ctx = EVP_CIPHER_CTX_new();
assert(cl->dec_ctx);
int r = EVP_DecryptInit_ex(cl->dec_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
if (r != 1)
{
fprintf(stderr, "DecryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
#endif
}
}
if (cl->peer_iv_ctr >= AES_256_GCM_MAX_IV_CTR)
{
// Rotate key every 2^32 messages
bool ok = msgr->derive_aes_keys(cl, false, true);
assert(ok);
}
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_init_256(&cl->peer_key_isal, cl->dec_ctx, cl->peer_key.data() + AES_256_GCM_KEY_SIZE, NULL, 0);
if (r != 0)
{
fprintf(stderr, "isal_aes_gcm_init_256 error %d\n", r);
abort();
}
#else
int r = EVP_DecryptInit_ex(cl->dec_ctx, NULL, NULL, cl->peer_key.data(), cl->peer_key.data() + AES_256_GCM_KEY_SIZE);
if (r != 1)
{
fprintf(stderr, "DecryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
#endif
// Increase IV
cl->peer_iv_ctr++;
(*(uint64_t*)(cl->peer_key.data() + AES_256_GCM_KEY_SIZE))++;
}
bool read(uint8_t *dst, size_t dst_len, int flags) override
@@ -300,66 +170,21 @@ public:
if (done >= bufsize)
return false;
size_t n = dst_len-from;
if (!(flags & RDR_TLS))
if (n > bufsize-done)
n = bufsize-done;
if (flags & RDR_XTS)
{
if (n > bufsize-done)
n = bufsize-done;
if (flags & RDR_XTS)
{
msgr->op_decrypted_copy_buf(cl, curbuf, bufsize, dst, dst_len, from, done);
n = 0;
}
else
{
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
{
// data may be skipped if dst == NULL but checksum is still calculated
XXH3_64bits_update(cl->read_csum_state, curbuf+done, n);
}
// Here, dst == NULL is allowed
if (dst != NULL)
memcpy(dst+from, curbuf+done, n);
done += n;
}
cl->read_op_pos += n;
from += n;
if (from < dst_len)
{
return false;
}
msgr->op_decrypted_copy_buf(cl, curbuf, bufsize, dst, dst_len, from, done);
n = 0;
}
else
else if (flags & RDR_GCM)
{
// Here, dst == NULL is not allowed
assert(dst != NULL);
if (cl->dec_batch_size_size < 4)
{
size_t n = 4-cl->dec_batch_size_size;
if (n > bufsize-done)
n = bufsize-done;
memcpy(&cl->dec_batch_size, curbuf+done, n);
cl->dec_batch_size_size += n;
done += n;
if (cl->dec_batch_size_size < 4)
return false;
if (!cl->dec_batch_size)
{
fprintf(stderr, "Client %ju - empty batch received, disconnecting\n", cl->client_id);
cl->io_error = true;
return false;
}
uint8_t iv[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
int r = EVP_DecryptInit_ex(cl->dec_ctx, NULL, NULL, (uint8_t*)msgr->test_osd_aes_key.data(), iv);
if (r != 1)
{
fprintf(stderr, "DecryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
}
size_t n = dst_len-from;
if (n > bufsize-done)
n = bufsize-done;
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_dec_256_update(&cl->peer_key_isal, cl->dec_ctx, dst+from, curbuf+done, n);
assert(!r);
#else
int actual_out;
if (EVP_DecryptUpdate(cl->dec_ctx, dst+from, &actual_out, curbuf+done, n) != 1)
{
@@ -368,17 +193,30 @@ public:
abort();
}
assert(actual_out == n);
#endif
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
{
XXH3_64bits_update(cl->read_csum_state, dst+from, n);
}
done += n;
from += n;
cl->read_op_pos += n;
if (from < dst_len)
}
else
{
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
{
return false;
// data may be skipped if dst == NULL but checksum is still calculated
XXH3_64bits_update(cl->read_csum_state, curbuf+done, n);
}
// Here, dst == NULL is allowed
if (dst != NULL)
memcpy(dst+from, curbuf+done, n);
done += n;
}
cl->read_op_pos += n;
from += n;
if (from < dst_len)
{
return false;
}
from = 0;
return true;
@@ -386,12 +224,6 @@ public:
bool finish() override
{
if (cl->dec_batch_size > 1)
{
// No tag yet
cl->dec_batch_size--;
return true;
}
if (cl->dec_tag_size+bufsize-done < 16)
{
// Buffer part of the tag
@@ -400,33 +232,62 @@ public:
done = bufsize;
return false;
}
#ifdef WITH_ISAL_CRYPTO
uint8_t calc_tag[16];
int r = isal_aes_gcm_dec_256_finalize(&cl->peer_key_isal, cl->dec_ctx, calc_tag, 16);
assert(r == 0);
if (cl->dec_tag_size > 0)
{
// Tag is partially buffered, append to it and compare
memcpy(cl->dec_tag+cl->dec_tag_size, curbuf+done, 16-cl->dec_tag_size);
done += 16-cl->dec_tag_size;
r = !memcmp(calc_tag, cl->dec_tag, 16);
}
else
{
// Compare the full tag directly from the source buffer
r = !memcmp(calc_tag, curbuf+done, 16);
done += 16;
}
#else
int r;
if (cl->dec_tag_size > 0)
{
// Tag is partially buffered, append to it and use it from there
memcpy(cl->dec_tag+cl->dec_tag_size, curbuf+done, 16-cl->dec_tag_size);
done += 16-cl->dec_tag_size;
r = EVP_CIPHER_CTX_ctrl(cl->dec_ctx, EVP_CTRL_GCM_SET_TAG, 16, cl->dec_tag);
assert(r == 1);
done += 16-cl->dec_tag_size;
}
else
{
// Take full tag directly from the source buffer
r = EVP_CIPHER_CTX_ctrl(cl->dec_ctx, EVP_CTRL_GCM_SET_TAG, 16, curbuf+done);
assert(r == 1);
done += 16;
}
assert(r == 1);
int len = 0;
r = EVP_DecryptFinal_ex(cl->dec_ctx, NULL, &len);
assert(len == 0);
#endif
if (r != 1)
{
fprintf(stderr, "Client %ju AES-GCM decryption failed\n", cl->client_id);
cl->io_error = true;
return false;
}
if (msgr->decrypt_gcm_pool.size() < msgr->max_cipher_pool_size)
msgr->decrypt_gcm_pool.push_back(cl->dec_ctx);
else
{
#ifdef WITH_ISAL_CRYPTO
free(cl->dec_ctx);
#else
EVP_CIPHER_CTX_free(cl->dec_ctx);
#endif
}
cl->dec_ctx = NULL;
cl->dec_tag_size = 0;
assert(len == 0);
cl->dec_batch_size = 0;
cl->dec_batch_size_size = 0;
return true;
}
@@ -486,17 +347,15 @@ public:
bool read(uint8_t *dst, size_t dst_len, int flags) override
{
if (cl->dec_ctx)
return false; // FIXME Only for tests, use copy-only with AES
if (from >= dst_len)
{
// Skip
from -= dst_len;
return true;
}
if ((flags & RDR_TLS) && cl->ssl_cli)
if ((flags & RDR_GCM) && cl->gcm_enabled)
{
// Can't inplace read TLS data
// Can't inplace read encrypted data
return false;
}
if (cl->recv_list.size() >= IOV_MAX)
@@ -520,7 +379,7 @@ public:
bool finish() override
{
if (cl->dec_ctx)
if (cl->gcm_enabled)
return false;
return true;
}
@@ -676,8 +535,6 @@ out_wakeup:
{
goto out_wakeup;
}
execute_verified_op(cl, cl->read_op);
cl->read_op = NULL;
}
}
cl->read_msg.msg_iovlen = 0;
@@ -718,8 +575,49 @@ void osd_messenger_t::handle_immediate_ops()
bool osd_messenger_t::handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize)
{
if (cl->dec_ctx)
if (cl->gcm_enabled)
{
if (cl->hs)
{
ssize_t done = cl->hs->handle(curbuf, bufsize);
if (done < 0)
{
fprintf(stderr, "Client %ju handshake failed: %s\n", cl->client_id, cl->hs->get_error().c_str());
stop_client(cl->client_id);
return false;
}
if (cl->hs->done() && !derive_aes_keys(cl, true, true))
{
stop_client(cl->client_id);
return false;
}
curbuf += done;
bufsize -= done;
if (cl->hs->out_size())
{
if (cl->write_state == 0)
{
cl->write_state = CL_WRITE_READY;
write_ready_clients.push_back(cl->client_id);
}
}
if (cl->hs->done() && !cl->hs->out_size())
{
// Delete hs when done and nothing to send
delete cl->hs;
cl->hs = NULL;
}
else
{
if (done < bufsize)
{
fprintf(stderr, "Client %ju extra data after handshake\n", cl->client_id);
stop_client(cl->client_id);
return false;
}
return true;
}
}
return handle_buffer_with<gcm_op_reader_t>(cl, curbuf, bufsize);
}
return handle_buffer_with<copy_op_reader_t>(cl, curbuf, bufsize);
@@ -750,7 +648,10 @@ bool osd_messenger_t::handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size
{
if (!cl->read_csum_state)
cl->read_csum_state = XXH3_createState();
XXH3_64bits_reset(cl->read_csum_state);
if (cl->peer_key.size() == AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE)
XXH3_64bits_reset_withSecret(cl->read_csum_state, cl->peer_key.data() + AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE, XXH_SECRET_DEFAULT_SIZE);
else
XXH3_64bits_reset(cl->read_csum_state);
}
if (!op_read_from(cl, rdr) || !handle_finished_op(cl))
{
@@ -761,28 +662,6 @@ bool osd_messenger_t::handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size
}
break;
}
if constexpr (std::is_same_v<T, gcm_op_reader_t>)
{
if (cl->dec_batch_size_size)
{
// Operation is not verified yet
cl->unverified_ops.push_back(cl->read_op);
}
else
{
for (auto & op: cl->unverified_ops)
{
execute_verified_op(cl, op);
}
cl->unverified_ops.clear();
execute_verified_op(cl, cl->read_op);
}
}
else
{
execute_verified_op(cl, cl->read_op);
}
cl->read_op = NULL;
}
assert(rdr.get_done() == bufsize);
return true;
@@ -841,18 +720,42 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
{
osd_op_t *cur_op = cl->read_op;
cl->read_op_size = 0;
if (cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
if (!osd_num)
{
if (log_level > 1)
fprintf(stderr, "Error: operation received from an OSD peer %ju, stopping\n", cl->client_id);
return false;
}
else if (cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
{
if (cur_op->req.sec_rw.attr_len > 0)
{
if (cur_op->req.sec_rw.attr_len > sizeof(unsigned))
if (cur_op->req.sec_rw.attr_len > clean_entry_bitmap_size)
{
if (log_level > 1)
{
fprintf(stderr, "Error: peer %ju secondary write request attr_len too large (%u > %u bytes), stopping\n", cl->client_id,
cur_op->req.sec_rw.attr_len, clean_entry_bitmap_size);
}
return false;
}
else if (cur_op->req.sec_rw.attr_len > sizeof(cur_op->bmp_data))
cur_op->bitmap = cur_op->rmw_buf = malloc_or_die(cur_op->req.sec_rw.attr_len);
else
cur_op->bitmap = &cur_op->bmp_data;
}
if (cur_op->req.sec_rw.len > 0)
{
if (cur_op->req.sec_rw.len > bs_block_size)
{
if (log_level > 1)
{
fprintf(stderr, "Error: peer %ju secondary write request size too large (%u > %u bytes), stopping\n", cl->client_id,
cur_op->req.sec_rw.len, bs_block_size);
}
return false;
}
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_rw.len);
}
cl->read_op_size = cur_op->req.sec_rw.len + cur_op->req.sec_rw.attr_len;
@@ -862,6 +765,15 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
{
if (cur_op->req.sec_stab.len > 0)
{
if (cur_op->req.sec_stab.len > MAX_SIMPLE_PAYLOAD_SIZE)
{
if (log_level > 1)
{
fprintf(stderr, "Error: peer %ju stabilize request size too large (%lu > %u bytes), stopping\n", cl->client_id,
cur_op->req.sec_stab.len, MAX_SIMPLE_PAYLOAD_SIZE);
}
return false;
}
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_stab.len);
}
cl->read_op_size = cur_op->req.sec_stab.len;
@@ -870,6 +782,15 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
{
if (cur_op->req.sec_read_bmp.len > 0)
{
if (cur_op->req.sec_read_bmp.len > MAX_SIMPLE_PAYLOAD_SIZE)
{
if (log_level > 1)
{
fprintf(stderr, "Error: peer %ju sec_read_bmp request size too large (%lu > %u bytes), stopping\n", cl->client_id,
cur_op->req.sec_read_bmp.len, MAX_SIMPLE_PAYLOAD_SIZE);
}
return false;
}
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_read_bmp.len);
}
cl->read_op_size = cur_op->req.sec_read_bmp.len;
@@ -878,6 +799,15 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
{
if (cur_op->req.rw.len > 0)
{
if (cur_op->req.rw.len > max_write_request_size)
{
if (log_level > 1)
{
fprintf(stderr, "Error: peer %ju write request size too large (%u > %u bytes), stopping\n", cl->client_id,
cur_op->req.rw.len, max_write_request_size);
}
return false;
}
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.rw.len);
}
cl->read_op_size = cur_op->req.rw.len;
@@ -886,6 +816,15 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
{
if (cur_op->req.show_conf.json_len > 0)
{
if (cur_op->req.show_conf.json_len > MAX_SIMPLE_PAYLOAD_SIZE)
{
if (log_level > 1)
{
fprintf(stderr, "Error: peer %ju show_config request length too large (%lu > %u bytes), stopping\n", cl->client_id,
cur_op->req.show_conf.json_len, MAX_SIMPLE_PAYLOAD_SIZE);
}
return false;
}
cur_op->buf = malloc_or_die(cur_op->req.show_conf.json_len+1);
((uint8_t*)cur_op->buf)[cur_op->req.show_conf.json_len] = 0;
}
@@ -914,7 +853,7 @@ bool osd_messenger_t::allocate_reply_buffers(osd_client_t *cl, osd_op_t *op)
cl->client_id, expected_size, op->bitmap_len, op->reply.hdr.retval, bmp_len);
return false;
}
if (bmp_len > 0)
if (op->reply.hdr.retval >= 0 && bmp_len > 0)
{
assert(op->bitmap);
cl->read_op_size += bmp_len;
@@ -964,7 +903,7 @@ bool osd_messenger_t::op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr)
bool hdr = (cl->read_op_pos < OSD_PACKET_SIZE);
if (hdr || op->op_type == OSD_OP_IN)
{
if (!rdr.read(op->req.buf, OSD_PACKET_SIZE, RDR_TLS | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? RDR_NO_CSUM : 0)))
if (!rdr.read(op->req.buf, OSD_PACKET_SIZE, RDR_GCM | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? RDR_NO_CSUM : 0)))
return false;
if (hdr)
{
@@ -980,49 +919,49 @@ bool osd_messenger_t::op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr)
if (op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
{
if (!rdr.read((uint8_t*)op->bitmap, op->req.sec_rw.attr_len, RDR_TLS))
if (!rdr.read((uint8_t*)op->bitmap, op->req.sec_rw.attr_len, RDR_GCM))
return false;
if (!rdr.read((uint8_t*)op->buf, op->req.sec_rw.len, 0))
if (!rdr.read((uint8_t*)op->buf, op->req.sec_rw.len, cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK)
{
if (!rdr.read((uint8_t*)op->buf, op->req.sec_stab.len, RDR_TLS))
if (!rdr.read((uint8_t*)op->buf, op->req.sec_stab.len, RDR_GCM))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
{
if (!rdr.read((uint8_t*)op->buf, op->req.sec_read_bmp.len, RDR_TLS))
if (!rdr.read((uint8_t*)op->buf, op->req.sec_read_bmp.len, RDR_GCM))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_WRITE)
{
if (!rdr.read((uint8_t*)op->buf, op->req.rw.len, 0))
if (!rdr.read((uint8_t*)op->buf, op->req.rw.len, cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)
{
if (!rdr.read((uint8_t*)op->buf, op->req.show_conf.json_len, RDR_TLS))
if (!rdr.read((uint8_t*)op->buf, op->req.show_conf.json_len, RDR_GCM))
return false;
}
}
else
{
if (!rdr.read(op->reply.buf, OSD_PACKET_SIZE, RDR_TLS | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? RDR_NO_CSUM : 0)))
if (!rdr.read(op->reply.buf, OSD_PACKET_SIZE, RDR_GCM | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? RDR_NO_CSUM : 0)))
return false;
switched_type:
if (op->reply.hdr.opcode == OSD_OP_SEC_READ)
{
if (op->reply.sec_rw.attr_len > 0)
{
if (!rdr.read((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len, RDR_TLS))
if (!rdr.read((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len, RDR_GCM))
return false;
}
if (op->reply.hdr.retval > 0)
{
for (int i = 0; i < op->iov.count; i++)
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, 0))
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, (cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0)))
return false;
}
}
@@ -1030,37 +969,37 @@ switched_type:
{
if (op->reply.rw.bitmap_len > 0)
{
if (!rdr.read((uint8_t*)op->bitmap, op->reply.rw.bitmap_len, RDR_TLS))
if (!rdr.read((uint8_t*)op->bitmap, op->reply.rw.bitmap_len, RDR_GCM))
return false;
}
if (op->reply.hdr.retval > 0)
{
for (int i = 0; i < op->iov.count; i++)
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, (op->enc ? RDR_XTS : 0)))
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, (op->enc ? RDR_XTS : 0) | (cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0)))
return false;
}
}
else if (op->reply.hdr.opcode == OSD_OP_SEC_LIST && op->reply.hdr.retval > 0)
{
if (!rdr.read((uint8_t*)op->buf, sizeof(obj_ver_id) * op->reply.hdr.retval, RDR_TLS))
if (!rdr.read((uint8_t*)op->buf, sizeof(obj_ver_id) * op->reply.hdr.retval, RDR_GCM))
return false;
}
else if ((op->reply.hdr.opcode == OSD_OP_SEC_READ_BMP ||
op->reply.hdr.opcode == OSD_OP_SHOW_CONFIG) && op->reply.hdr.retval > 0)
{
if (!rdr.read((uint8_t*)op->buf, op->reply.hdr.retval, RDR_TLS))
if (!rdr.read((uint8_t*)op->buf, op->reply.hdr.retval, RDR_GCM))
return false;
}
else if (op->reply.hdr.opcode == OSD_OP_DESCRIBE && op->reply.describe.result_bytes > 0)
{
if (!rdr.read((uint8_t*)op->buf, op->reply.describe.result_bytes, RDR_TLS))
if (!rdr.read((uint8_t*)op->buf, op->reply.describe.result_bytes, RDR_GCM))
return false;
}
}
if (cl->proto_csum_status == MSGR_CSUM_FULL ||
cl->read_op_size > 0 && cl->proto_csum_status == MSGR_CSUM_PAYLOAD)
{
if (!rdr.read((uint8_t*)&op->csum, 8, RDR_TLS|RDR_NO_CSUM))
if (!rdr.read((uint8_t*)&op->csum, 8, RDR_GCM|RDR_NO_CSUM))
return false;
}
if (!rdr.finish())
@@ -1088,21 +1027,6 @@ bool osd_messenger_t::handle_finished_op(osd_client_t *cl)
return false;
}
}
if (op->op_type == OSD_OP_OUT)
{
// Inline decryption
if (cl->read_op_inline_decrypt_pos != (size_t)-1)
{
op_decrypt_inline(cl);
cl->read_op_inline_decrypt_pos = (size_t)-1;
}
}
op_decrypt_free(cl);
return true;
}
void osd_messenger_t::execute_verified_op(osd_client_t *cl, osd_op_t *op)
{
if (op->op_type == OSD_OP_IN)
{
// Operation is ready
@@ -1110,6 +1034,12 @@ void osd_messenger_t::execute_verified_op(osd_client_t *cl, osd_op_t *op)
}
else
{
// Inline decryption
if (cl->read_op_inline_decrypt_pos != (size_t)-1)
{
op_decrypt_inline(cl);
cl->read_op_inline_decrypt_pos = (size_t)-1;
}
// Measure subop (outbound op) latency
timespec tv_end;
clock_gettime(CLOCK_REALTIME, &tv_end);
@@ -1124,5 +1054,8 @@ void osd_messenger_t::execute_verified_op(osd_client_t *cl, osd_op_t *op)
(tv_end.tv_nsec - op->tv_begin.tv_nsec)/1000
);
}
op_decrypt_free(cl);
set_immediate_ops.push_back(op);
cl->read_op = NULL;
return true;
}
+256 -457
View File
@@ -6,18 +6,17 @@
#include <sys/epoll.h>
#include "messenger.h"
#include "msgr_iothread.h"
#ifdef WITH_OPENSSL
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#endif
#define WR_TLS 1
#define WR_GCM 1
#define WR_XTS 2
#define WR_NO_CSUM 4
#define GCM_TMP_BUF_SIZE 4096
class msgr_op_writer_t
{
public:
@@ -86,147 +85,6 @@ public:
}
};
class ssl_op_writer_t: public msgr_op_writer_t
{
osd_messenger_t* msgr;
osd_client_t* cl;
size_t from;
uint8_t *curbuf;
size_t bufsize;
size_t done;
public:
ssl_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t *curbuf, size_t bufsize):
msgr(msgr), cl(cl), from(cl->write_op_pos), curbuf(curbuf), bufsize(bufsize), done(0)
{
}
void reset()
{
from = cl->write_op_pos;
}
void flush_ssl()
{
if (!cl->ssl_handshake_done)
{
if (!msgr->ssl_do_handshake(cl))
return;
}
_flush_ssl();
}
bool _flush_ssl()
{
int r = BIO_read(cl->read_from_ssl, curbuf+done, bufsize-done);
if (r > 0)
done += r;
if (done >= bufsize)
{
// Check if we've sent all buffered TLS data
// ...Because we can't return true from this->write() if we haven't
char *bio_buf = NULL;
size_t bio_sz = BIO_get_mem_data(cl->read_from_ssl, &bio_buf);
if (bio_sz > 0)
{
cl->ssl_more_to_buffer = true;
return false;
}
}
return true;
}
static inline bool write_to_ssl(osd_client_t *cl, uint8_t *src, size_t src_len, int flags, size_t & from)
{
size_t n = src_len-from;
int ok = SSL_write_ex(cl->ssl_cli, src+from, n, &n);
if (ok)
{
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
cl->write_op_pos += n;
from += n;
}
else
{
ok = SSL_get_error(cl->ssl_cli, ok);
if (ok == SSL_ERROR_ZERO_RETURN)
{
fprintf(stderr, "Client %ju TLS disconnected\n", cl->client_id);
cl->io_error = true;
return false;
}
else if (ok != SSL_ERROR_WANT_READ && ok != SSL_ERROR_WANT_WRITE)
{
fprintf(stderr, "Client %ju TLS write error: %s. Disconnecting client\n", cl->client_id, ERR_error_string(ERR_get_error(), NULL));
cl->io_error = true;
return false;
}
}
return true;
}
bool write(uint8_t *src, size_t src_len, int flags) override
{
if (from >= src_len)
{
if (cl->ssl_more_to_buffer && !_flush_ssl())
return false;
from -= src_len;
return true;
}
if (!(flags & WR_TLS) || !cl->ssl_cli)
{
if (flags & WR_XTS)
{
msgr->op_encrypted_copy_buf(cl, curbuf, bufsize, src, src_len, from, done);
}
else
{
size_t n = src_len-from;
if (n > bufsize-done)
n = bufsize-done;
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
memcpy(curbuf+done, src+from, n);
done += n;
cl->write_op_pos += n;
from += n;
}
}
else
{
if (!cl->ssl_handshake_done)
{
if (!msgr->ssl_do_handshake(cl))
return false;
}
if (cl->ssl_handshake_done)
{
if (!write_to_ssl(cl, src, src_len, flags, from))
return false;
}
if (!_flush_ssl())
return false;
}
if (from < src_len)
return false;
from = 0;
return true;
}
bool finish() override
{
return _flush_ssl();
}
size_t get_done()
{
return done;
}
};
class gcm_op_writer_t: public msgr_op_writer_t
{
osd_messenger_t* msgr;
@@ -236,17 +94,85 @@ class gcm_op_writer_t: public msgr_op_writer_t
uint8_t *curbuf;
size_t bufsize;
size_t done;
uint32_t *batch_size_ptr;
public:
gcm_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t *curbuf, size_t bufsize):
msgr(msgr), cl(cl), from(cl->write_op_pos), curbuf(curbuf), bufsize(bufsize), done(0), batch_size_ptr(NULL)
msgr(msgr), cl(cl), from(cl->write_op_pos), curbuf(curbuf), bufsize(bufsize), done(0)
{
}
void reset()
{
from = cl->write_op_pos;
init_ctx(msgr, cl);
}
static void init_ctx(osd_messenger_t* msgr, osd_client_t *cl)
{
if (!cl->enc_ctx)
{
if (msgr->encrypt_gcm_pool.size())
{
cl->enc_ctx = msgr->encrypt_gcm_pool.back();
msgr->encrypt_gcm_pool.pop_back();
}
else
{
#ifdef WITH_ISAL_CRYPTO
cl->enc_ctx = (isal_gcm_context_data*)malloc_or_die(sizeof(isal_gcm_context_data));
#else
cl->enc_ctx = EVP_CIPHER_CTX_new();
assert(cl->enc_ctx);
int r = EVP_EncryptInit_ex(cl->enc_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
if (r != 1)
{
fprintf(stderr, "EncryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
#endif
}
}
if (cl->my_iv_ctr >= AES_256_GCM_MAX_IV_CTR)
{
// Rotate key every 2^32 messages
bool ok = msgr->derive_aes_keys(cl, true, false);
assert(ok);
}
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_init_256(&cl->my_key_isal, cl->enc_ctx, cl->my_key.data() + AES_256_GCM_KEY_SIZE, NULL, 0);
if (r != 0)
{
fprintf(stderr, "isal_aes_gcm_init_256 error %d\n", r);
abort();
}
#else
int r = EVP_EncryptInit_ex(cl->enc_ctx, NULL, NULL, (uint8_t*)cl->my_key.data(), cl->my_key.data() + AES_256_GCM_KEY_SIZE);
if (r != 1)
{
fprintf(stderr, "EncryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
#endif
// Increase IV
cl->my_iv_ctr++;
(*(uint64_t*)(cl->my_key.data() + AES_256_GCM_KEY_SIZE))++;
}
static void free_ctx(osd_messenger_t* msgr, osd_client_t *cl)
{
if (msgr->encrypt_gcm_pool.size() < msgr->max_cipher_pool_size)
msgr->encrypt_gcm_pool.push_back(cl->enc_ctx);
else
{
#ifdef WITH_ISAL_CRYPTO
free(cl->enc_ctx);
#else
EVP_CIPHER_CTX_free(cl->enc_ctx);
#endif
}
cl->enc_ctx = NULL;
}
bool write(uint8_t *src, size_t src_len, int flags) override
@@ -256,60 +182,21 @@ public:
from -= src_len;
return true;
}
if (!(flags & WR_TLS))
if (flags & WR_XTS)
{
if (flags & WR_XTS)
{
msgr->op_encrypted_copy_buf(cl, curbuf, bufsize, src, src_len, from, done);
}
else
{
size_t n = src_len-from;
if (n > bufsize-done)
n = bufsize-done;
if (!n)
return false;
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
memcpy(curbuf+done, src+from, n);
done += n;
cl->write_op_pos += n;
from += n;
}
msgr->op_encrypted_copy_buf(cl, curbuf, bufsize, src, src_len, from, done);
}
else
else if (flags & WR_GCM)
{
if (!cl->write_op_pos)
{
if (batch_size_ptr)
{
if (bufsize-done < 1)
return false;
(*batch_size_ptr)++;
}
else
{
if (bufsize-done < 5)
return false;
batch_size_ptr = (uint32_t*)(curbuf+done);
*batch_size_ptr = 1; // FIXME like header, but now for tests
done += 4;
cl->enc_batch = true;
uint8_t iv[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
int r = EVP_EncryptInit_ex(cl->enc_ctx, NULL, NULL, (uint8_t*)msgr->test_osd_aes_key.data(), iv);
if (r != 1)
{
fprintf(stderr, "EncryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
}
}
size_t n = src_len-from;
if (n > bufsize-done)
n = bufsize-done;
if (!n)
return false;
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, curbuf+done, src+from, n);
assert(!r);
#else
int actual_out;
if (EVP_EncryptUpdate(cl->enc_ctx, curbuf+done, &actual_out, src+from, n) != 1)
{
@@ -318,20 +205,39 @@ public:
abort();
}
assert(actual_out == n);
#endif
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
done += n;
cl->write_op_pos += n;
from += n;
}
else
{
size_t n = src_len-from;
if (n > bufsize-done)
n = bufsize-done;
if (!n)
return false;
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
memcpy(curbuf+done, src+from, n);
done += n;
cl->write_op_pos += n;
from += n;
}
if (from < src_len)
return false;
from = 0;
return true;
}
static void write_tag_to(osd_client_t *cl, uint8_t *dst)
static void write_tag_to(osd_messenger_t *msgr, osd_client_t *cl, uint8_t *dst)
{
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_enc_256_finalize(&cl->my_key_isal, cl->enc_ctx, dst, 16);
assert(!r);
#else
int actual_out = 0;
int r = EVP_EncryptFinal_ex(cl->enc_ctx, NULL, &actual_out);
if (r != 1)
@@ -343,26 +249,38 @@ public:
assert(actual_out == 0);
r = EVP_CIPHER_CTX_ctrl(cl->enc_ctx, EVP_CTRL_GCM_GET_TAG, 16, dst);
assert(r == 1);
#endif
}
bool finish() override
{
if (bufsize-done >= OSD_PACKET_SIZE+16 && batch_size_ptr && cl->write_ops.size())
{
// More operations may fit, so don't finish the batch yet
return true;
}
// Tag is 16 bytes
if (bufsize-done < 16)
{
// No space for the tag
if (done >= bufsize)
return false;
if (bufsize-done < 16 || cl->enc_tag_size)
{
// No space for the full tag, but msgr_rdma expects us to always fill the whole buffer
if (!cl->enc_tag_size)
{
write_tag_to(msgr, cl, cl->enc_tag);
cl->enc_tag_size = 16;
}
size_t n = bufsize-done;
if (n > cl->enc_tag_size)
n = cl->enc_tag_size;
memcpy(curbuf+done, cl->enc_tag+16-cl->enc_tag_size, n);
done += n;
cl->enc_tag_size -= n;
if (cl->enc_tag_size > 0)
return false;
}
write_tag_to(cl, curbuf+done);
done += 16;
// Batch is completed
cl->enc_batch = false;
batch_size_ptr = NULL;
else
{
// The whole tag fits at once
write_tag_to(msgr, cl, curbuf+done);
done += 16;
}
free_ctx(msgr, cl);
return true;
}
@@ -372,98 +290,53 @@ public:
}
};
// FIXME Split into 3 classes - basic, tls and gcm
class get_op_writer_t: public msgr_op_writer_t
{
osd_messenger_t* msgr;
osd_client_t* cl;
size_t from;
size_t done;
size_t op_enc;
size_t enc_size;
size_t done_enc;
bool have_batch;
size_t batch_size_offset;
size_t batch_bytes;
void ssl_extend_buf(size_t more = 0)
{
size_t min_cap = cl->ssl_out_buf_size*2;
if (min_cap < cl->ssl_out_buf_size+more)
min_cap = cl->ssl_out_buf_size+more;
if (min_cap < 16384)
min_cap = 16384;
if (cl->ssl_out_buf_cap < min_cap)
{
uint8_t *old_buf = cl->ssl_out_buf;
uint8_t *old_end = old_buf + cl->ssl_out_buf_cap;
cl->ssl_out_buf = (uint8_t*)realloc_or_die(cl->ssl_out_buf, min_cap);
cl->ssl_out_buf_cap = min_cap;
for (auto & iov: cl->send_list)
{
if (iov.iov_base >= old_buf && iov.iov_base < old_end)
iov.iov_base = cl->ssl_out_buf + ((uint8_t*)iov.iov_base - old_buf);
}
}
}
void copy_ssl()
{
size_t prev_size = cl->ssl_out_buf_size;
do
{
ssl_extend_buf();
int r = BIO_read(cl->read_from_ssl, cl->ssl_out_buf+cl->ssl_out_buf_size, cl->ssl_out_buf_cap-cl->ssl_out_buf_size);
if (r > 0)
cl->ssl_out_buf_size += r;
} while (cl->ssl_out_buf_size >= cl->ssl_out_buf_cap);
if (cl->ssl_out_buf_size > prev_size)
{
cl->send_list.push_back((iovec){ .iov_base = cl->ssl_out_buf+prev_size, .iov_len = cl->ssl_out_buf_size-prev_size });
}
}
uint8_t *enc_buf;
public:
get_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl):
msgr(msgr), cl(cl), from(cl->write_op_pos), enc_size(0), done_enc(0),
have_batch(false), batch_size_offset(0), batch_bytes(0)
get_op_writer_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t*, size_t):
msgr(msgr), cl(cl), from(cl->write_op_pos), done(0), enc_size(0), done_enc(0), enc_buf(NULL)
{
}
void reset()
{
op_enc = 0;
from = cl->write_op_pos;
enc_size = 0;
done_enc = 0;
if (cl->gcm_enabled)
{
gcm_op_writer_t::init_ctx(msgr, cl);
}
}
void flush_ssl()
void extend_tmp(size_t n)
{
if (!cl->ssl_handshake_done)
if (!enc_buf || done_enc + n > enc_size)
{
if (!msgr->ssl_do_handshake(cl))
return;
enc_size = n < GCM_TMP_BUF_SIZE ? GCM_TMP_BUF_SIZE : n;
enc_buf = (uint8_t*)malloc_or_die(enc_size);
done_enc = 0;
assert(!((size_t)enc_buf & 7));
cl->send_free_ops.push_back((osd_op_t*)((size_t)enc_buf | 1));
}
if (cl->send_list.size() >= IOV_MAX)
{
return;
}
copy_ssl();
}
void send_out_buf(size_t n)
void send_tmp(size_t n)
{
if (cl->send_list.size() > 0)
{
iovec& last = cl->send_list.back();
if (last.iov_base+last.iov_len == cl->ssl_out_buf+cl->ssl_out_buf_size)
{
last.iov_len += n;
cl->ssl_out_buf_size += n;
return;
}
}
cl->send_list.push_back((iovec){ .iov_base = cl->ssl_out_buf+cl->ssl_out_buf_size, .iov_len = n });
cl->ssl_out_buf_size += n;
if (cl->send_list.size() && cl->send_list.back().iov_base == (enc_buf + done_enc))
cl->send_list.back().iov_len += n;
else
cl->send_list.push_back((iovec){ .iov_base = enc_buf + done_enc, .iov_len = n });
done += n;
done_enc += n;
}
bool write(uint8_t *src, size_t src_len, int flags) override
@@ -474,109 +347,61 @@ public:
from -= src_len;
return true;
}
if (cl->send_list.size() >= IOV_MAX)
if (cl->send_list.size() >= IOV_MAX-1)
{
// Make sure tag always fits
return false;
}
if (flags & WR_TLS)
{
if (cl->ssl_cli)
{
if (!cl->ssl_handshake_done)
{
if (!msgr->ssl_do_handshake(cl))
return false;
}
if (cl->ssl_handshake_done)
{
if (!ssl_op_writer_t::write_to_ssl(cl, src, src_len, flags, from))
return false;
}
// Copy data to client's temporary SSL output buffer
copy_ssl();
if (from < src_len)
return false;
from = 0;
return true;
}
else if (cl->enc_ctx)
{
// Encrypt data to client's temporary output buffer (all at once)
if (!cl->write_op_pos)
{
if (have_batch)
{
(*(uint32_t*)(cl->ssl_out_buf+batch_size_offset))++;
}
else
{
ssl_extend_buf(4);
have_batch = true;
batch_size_offset = cl->ssl_out_buf_size;
(*(uint32_t*)(cl->ssl_out_buf+batch_size_offset)) = 1;
send_out_buf(4);
cl->enc_batch = true;
uint8_t iv[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
int r = EVP_EncryptInit_ex(cl->enc_ctx, NULL, NULL, (uint8_t*)msgr->test_osd_aes_key.data(), iv);
if (r != 1)
{
fprintf(stderr, "EncryptInit error: ");
ERR_print_errors_fp(stderr);
abort();
}
}
}
size_t n = src_len-from;
ssl_extend_buf(n);
int actual_out;
if (EVP_EncryptUpdate(cl->enc_ctx, cl->ssl_out_buf+cl->ssl_out_buf_size, &actual_out, src+from, n) != 1)
{
fprintf(stderr, "EncryptUpdate error: ");
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == n);
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
send_out_buf(n);
batch_bytes += n;
cl->write_op_pos += n;
from += n;
if (from < src_len)
return false;
from = 0;
return true;
}
}
if (flags & WR_XTS)
{
if (!cl->write_op->enc_buf)
// Allocate a temporary buffer and encrypt data to it
if (!op_enc)
{
if (cl->send_list.size() >= IOV_MAX-1)
{
// Make sure that 1 encrypted buffer and 1 checksum fits
return false;
}
// No way except than to allocate a temporary buffer and encrypt data to it
assert(cl->write_op->req.hdr.opcode == OSD_OP_WRITE);
enc_size = cl->write_op->req.rw.len - from + (from % 16);
assert(enc_size > 0);
cl->write_op->enc_buf = (uint8_t*)malloc_or_die(enc_size);
cl->send_list.push_back((iovec){ .iov_base = cl->write_op->enc_buf, .iov_len = enc_size });
op_enc = cl->write_op->req.rw.len - from + (from % 16);
assert(op_enc > 0);
extend_tmp(op_enc);
}
assert(enc_size > 0);
size_t old_from = from;
msgr->op_encrypted_copy_buf(cl, cl->write_op->enc_buf, enc_size, src, src_len, from, done_enc);
size_t new_done = done_enc;
msgr->op_encrypted_copy_buf(cl, enc_buf, enc_size, src, src_len, from, new_done);
send_tmp(new_done-done_enc);
assert(from == src_len);
batch_bytes += src_len-old_from;
}
else if ((flags & WR_GCM) && cl->gcm_enabled)
{
// Allocate a temporary buffer and encrypt data to it
size_t n = src_len-from;
extend_tmp(n);
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, enc_buf+done_enc, src+from, n);
assert(!r);
#else
int actual_out;
if (EVP_EncryptUpdate(cl->enc_ctx, enc_buf+done_enc, &actual_out, src+from, n) != 1)
{
fprintf(stderr, "EncryptUpdate error: ");
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == n);
#endif
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, n);
send_tmp(n);
cl->write_op_pos += n;
from += n;
if (from < src_len)
return false;
from = 0;
return true;
}
else
{
if (cl->write_csum_state && !(flags & WR_NO_CSUM))
XXH3_64bits_update(cl->write_csum_state, src+from, src_len-from);
cl->send_list.push_back((iovec){ src+from, src_len-from });
done += src_len-from;
cl->write_op_pos += src_len-from;
batch_bytes += src_len-from;
}
from = 0;
return true;
@@ -584,31 +409,21 @@ public:
bool finish() override
{
if (cl->ssl_cli)
if (cl->enc_ctx)
{
if (cl->send_list.size() >= IOV_MAX)
return false;
copy_ssl();
}
else if (cl->enc_ctx)
{
if (cl->send_list.size() >= IOV_MAX)
return false;
if (batch_bytes < 131072 && have_batch && cl->write_ops.size())
{
// More operations may fit, so don't finish the batch yet
return true;
}
// Tag is 16 bytes
ssl_extend_buf(16);
gcm_op_writer_t::write_tag_to(cl, cl->ssl_out_buf+cl->ssl_out_buf_size);
send_out_buf(16);
cl->enc_batch = false;
have_batch = false;
batch_bytes = 0;
extend_tmp(16);
gcm_op_writer_t::write_tag_to(msgr, cl, enc_buf + done_enc);
send_tmp(16);
gcm_op_writer_t::free_ctx(msgr, cl);
}
return true;
}
size_t get_done()
{
return done;
}
};
void osd_messenger_t::outbox_push(osd_op_t *cur_op)
@@ -720,30 +535,6 @@ void osd_messenger_t::measure_exec(osd_op_t *cur_op)
}
}
bool osd_messenger_t::ssl_do_handshake(osd_client_t *cl)
{
if (cl->ssl_handshake_done)
{
return true;
}
int r = SSL_do_handshake(cl->ssl_cli);
if (r > 0)
{
cl->ssl_handshake_done = true;
}
else
{
r = SSL_get_error(cl->ssl_cli, r);
if (r != 0 && r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE)
{
fprintf(stderr, "Client %ju TLS handshake error: %s, stopping client\n", cl->client_id, ERR_error_string(ERR_get_error(), NULL));
cl->io_error = true;
return false;
}
}
return true;
}
bool osd_messenger_t::try_send(osd_client_t *cl)
{
if (cl->peer_state == PEER_STOPPED || cl->peer_fd < 0)
@@ -755,32 +546,33 @@ bool osd_messenger_t::try_send(osd_client_t *cl)
return false;
}
assert(cl->peer_state != PEER_RDMA);
get_op_writer_t wr(this, cl);
while ((cl->write_op || cl->write_ops.size()) && cl->send_list.size() < IOV_MAX)
if (cl->hs)
{
if (!cl->write_op)
// Send handshake message
if (cl->hs->out_size())
{
next_write_op(cl);
wr.reset();
uint8_t *out = cl->hs->get_out();
cl->send_list.push_back((iovec){ .iov_base = out, .iov_len = cl->hs->out_size() });
assert(!((size_t)out & 7));
cl->send_free_ops.push_back((osd_op_t*)((size_t)out | 1));
cl->hs->reset_out();
}
osd_op_t *op = cl->write_op;
if (!op_write_to(cl, wr))
if (!cl->hs->out_size() && cl->hs->done())
{
if (cl->io_error)
{
stop_client(cl->client_id);
return true;
}
break;
}
if (!cl->write_op && op->op_type == OSD_OP_IN)
{
cl->send_free_ops.push_back(op);
delete cl->hs;
cl->hs = NULL;
goto copy_ops;
}
}
if (!cl->send_list.size() && cl->ssl_cli)
else
{
wr.flush_ssl();
copy_ops:
copy_ops_to_with<get_op_writer_t>(cl, NULL, 0);
}
if (cl->io_error)
{
stop_client(cl->client_id);
return true;
}
if (!cl->send_list.size())
{
@@ -846,8 +638,26 @@ bool osd_messenger_t::try_send(osd_client_t *cl)
size_t osd_messenger_t::copy_ops_to(osd_client_t *cl, uint8_t *dst, size_t dst_len)
{
if (cl->enc_ctx)
if (cl->gcm_enabled)
{
if (cl->hs)
{
// Send handshake message
size_t n = 0;
if (cl->hs->out_size())
{
n = cl->hs->out_size() < dst_len ? cl->hs->out_size() : dst_len;
memcpy(dst, cl->hs->get_out(), n);
cl->hs->eat_out(n);
}
if (!cl->hs->out_size() && cl->hs->done())
{
delete cl->hs;
cl->hs = NULL;
n += copy_ops_to_with<gcm_op_writer_t>(cl, dst+n, dst_len-n);
}
return n;
}
return copy_ops_to_with<gcm_op_writer_t>(cl, dst, dst_len);
}
return copy_ops_to_with<copy_op_writer_t>(cl, dst, dst_len);
@@ -861,8 +671,8 @@ size_t osd_messenger_t::copy_ops_to_with(osd_client_t *cl, uint8_t *dst, size_t
{
if (!cl->write_op)
{
next_write_op(cl);
wr.reset();
next_write_op(cl);
}
osd_op_t *op = cl->write_op;
if (!op_write_to(cl, wr))
@@ -877,10 +687,6 @@ size_t osd_messenger_t::copy_ops_to_with(osd_client_t *cl, uint8_t *dst, size_t
cl->send_free_ops.push_back(op);
}
}
/*FIXME if (!wr.get_done() && cl->ssl_cli)
{
wr.flush_ssl();
}*/
return wr.get_done();
}
@@ -892,7 +698,10 @@ void osd_messenger_t::next_write_op(osd_client_t *cl)
{
if (!cl->write_csum_state)
cl->write_csum_state = XXH3_createState();
XXH3_64bits_reset(cl->write_csum_state);
if (cl->my_key.size() == AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE)
XXH3_64bits_reset_withSecret(cl->write_csum_state, cl->my_key.data() + AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE, XXH_SECRET_DEFAULT_SIZE);
else
XXH3_64bits_reset(cl->write_csum_state);
}
}
@@ -964,24 +773,14 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
for (auto op: cl->send_free_ops)
{
if (more)
{
assert(!((size_t)op & 7));
cl->zc_free_list.push_back(op);
}
else
else if (!((size_t)op & 7))
delete op;
else
free((void*)((size_t)op & ~(size_t)7));
}
if (more)
{
if (cl->ssl_out_buf_size)
{
cl->zc_free_list.push_back((osd_op_t*)((size_t)cl->ssl_out_buf | 1));
cl->ssl_out_buf = NULL;
cl->ssl_out_buf_cap = 0;
}
cl->zc_free_list.push_back(NULL); // end marker
}
cl->ssl_out_buf_size = 0;
cl->send_free_ops.clear();
cl->write_state = 0;
if (cl->write_op || cl->write_ops.size())
@@ -1037,7 +836,7 @@ bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
osd_op_t *op = cl->write_op;
// Header
if (!wr.write((op->op_type == OSD_OP_IN ? op->reply.buf : op->req.buf), OSD_PACKET_SIZE,
WR_TLS | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? WR_NO_CSUM : 0)))
WR_GCM | (cl->proto_csum_status == MSGR_CSUM_PAYLOAD ? WR_NO_CSUM : 0)))
{
return false;
}
@@ -1046,17 +845,17 @@ bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
{
if (op->req.hdr.opcode == OSD_OP_SEC_READ && op->reply.sec_rw.attr_len > 0)
{
if (!wr.write((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len, WR_TLS))
if (!wr.write((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len, WR_GCM))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP && op->reply.hdr.retval > 0)
{
if (!wr.write((uint8_t*)op->buf, (size_t)op->reply.hdr.retval, WR_TLS))
if (!wr.write((uint8_t*)op->buf, (size_t)op->reply.hdr.retval, WR_GCM))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_READ && op->reply.rw.bitmap_len > 0)
{
if (!wr.write((uint8_t*)op->bitmap, op->reply.rw.bitmap_len, WR_TLS))
if (!wr.write((uint8_t*)op->bitmap, op->reply.rw.bitmap_len, WR_GCM))
return false;
}
}
@@ -1065,12 +864,12 @@ bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
if ((op->req.hdr.opcode == OSD_OP_SEC_WRITE || op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE) &&
op->req.sec_rw.attr_len > 0)
{
if (!wr.write((uint8_t*)op->bitmap, op->req.sec_rw.attr_len, WR_TLS))
if (!wr.write((uint8_t*)op->bitmap, op->req.sec_rw.attr_len, WR_GCM))
return false;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP && op->req.sec_read_bmp.len > 0)
{
if (!wr.write((uint8_t*)op->buf, (size_t)op->req.sec_read_bmp.len, WR_TLS))
if (!wr.write((uint8_t*)op->buf, (size_t)op->req.sec_read_bmp.len, WR_GCM))
return false;
}
}
@@ -1080,7 +879,7 @@ bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
for (int i = 0; i < cl->write_op->iov.count; i++)
{
auto & iov = cl->write_op->iov.buf[i];
if (!wr.write((uint8_t*)iov.iov_base, iov.iov_len, WR_TLS))
if (!wr.write((uint8_t*)iov.iov_base, iov.iov_len, WR_GCM))
return false;
}
}
@@ -1089,7 +888,7 @@ bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
for (int i = 0; i < cl->write_op->iov.count; i++)
{
auto & iov = cl->write_op->iov.buf[i];
if (!wr.write((uint8_t*)iov.iov_base, iov.iov_len, (op->enc ? WR_XTS : 0)))
if (!wr.write((uint8_t*)iov.iov_base, iov.iov_len, (op->enc ? WR_XTS : 0) | (cl->proto_csum_status == MSGR_CSUM_GCM ? WR_GCM : 0)))
return false;
}
}
@@ -1097,7 +896,7 @@ bool osd_messenger_t::op_write_to(osd_client_t *cl, msgr_op_writer_t & wr)
cl->proto_csum_status == MSGR_CSUM_PAYLOAD && cl->write_op_pos > OSD_PACKET_SIZE)
{
cl->write_op->csum = XXH3_64bits_digest(cl->write_csum_state);
if (!wr.write((uint8_t*)&cl->write_op->csum, 8, WR_TLS|WR_NO_CSUM))
if (!wr.write((uint8_t*)&cl->write_op->csum, 8, WR_GCM|WR_NO_CSUM))
return false;
}
if (!wr.finish())
+36 -58
View File
@@ -6,15 +6,8 @@
#include "messenger.h"
#include "../util/xxh_x86dispatch.h"
#ifdef WITH_RDMA
#include "msgr_rdma.h"
#endif
#ifdef WITH_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#endif
void osd_client_t::cancel_ops()
{
@@ -86,21 +79,21 @@ void osd_messenger_t::stop_client(uint64_t client_id, bool force_delete)
fprintf(stderr, "[OSD %ju] Stopping client %ju (regular client)\n", osd_num, client_id);
}
}
if (cl->encrypt_ctx)
if (cl->xts_enc_ctx)
{
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
destroy_aes_xts_encrypt(cl->encrypt_ctx);
if (encrypt_xts_pool.size() > max_cipher_pool_size)
destroy_aes_xts_encrypt(cl->xts_enc_ctx);
else
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
cl->encrypt_ctx = NULL;
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
cl->xts_enc_ctx = NULL;
}
if (cl->decrypt_ctx)
if (cl->xts_dec_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
destroy_aes_xts_decrypt(cl->decrypt_ctx);
if (decrypt_xts_pool.size() > max_cipher_pool_size)
destroy_aes_xts_decrypt(cl->xts_dec_ctx);
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
cl->xts_dec_ctx = NULL;
}
// First set state to STOPPED so another stop_client() call doesn't try to free it again
cl->refs++;
@@ -115,23 +108,11 @@ void osd_messenger_t::stop_client(uint64_t client_id, bool force_delete)
osd_peers.erase(osd_it);
}
}
#ifdef WITH_RDMA
if (cl->rdma_conn && cl->rdma_conn->cmid)
{
auto rdma_it = rdmacm_connections.find(cl->rdma_conn->cmid);
if (rdma_it != rdmacm_connections.end() && rdma_it->second == cl)
{
rdmacm_connections.erase(rdma_it);
}
}
#endif
#ifndef __MOCK__
if (cl->connect_timeout_id >= 0)
{
tfd->clear_timer(cl->connect_timeout_id);
cl->connect_timeout_id = -1;
}
#endif
if (cl->in_osd_num && break_pg_locks)
{
// Break PG locks
@@ -166,9 +147,7 @@ void osd_messenger_t::destroy_client(osd_client_t *cl)
clients.erase(cl->client_id);
if (cl->peer_fd >= 0)
{
#ifndef __MOCK__
tfd->set_fd_handler(cl->peer_fd, false, NULL);
#endif
for (auto rit = read_ready_clients.begin(); rit != read_ready_clients.end(); rit++)
{
if (*rit == cl->client_id)
@@ -187,6 +166,13 @@ void osd_messenger_t::destroy_client(osd_client_t *cl)
}
clients_by_fd.erase(cl->peer_fd);
}
#ifdef WITH_RDMA
if (cl->rdma_conn)
{
destroy_rdma_conn(cl->rdma_conn);
cl->rdma_conn = NULL;
}
#endif
delete cl;
}
@@ -210,19 +196,16 @@ osd_client_t::~osd_client_t()
read_op->cancel();
read_op = NULL;
}
while (unverified_ops.size())
{
auto op = unverified_ops.back();
unverified_ops.pop_back();
op->cancel();
}
// Cancel outbound ops
cancel_ops();
for (osd_op_t *op: send_free_ops)
{
if (op)
{
delete op;
if (!((size_t)op & 7))
delete op;
else
free((void*)((size_t)op & ~(size_t)7));
}
}
for (osd_op_t *op: zc_free_list)
@@ -235,15 +218,6 @@ osd_client_t::~osd_client_t()
free((void*)((size_t)op & ~(size_t)7));
}
}
#ifndef __MOCK__
#ifdef WITH_RDMA
if (rdma_conn)
{
delete rdma_conn;
rdma_conn = NULL;
}
#endif
#endif
if (read_csum_state)
{
XXH3_freeState(read_csum_state);
@@ -254,28 +228,32 @@ osd_client_t::~osd_client_t()
XXH3_freeState(write_csum_state);
write_csum_state = NULL;
}
#ifdef WITH_OPENSSL
if (enc_ctx)
{
#ifdef WITH_ISAL_CRYPTO
free(enc_ctx);
#else
EVP_CIPHER_CTX_free(enc_ctx);
#endif
enc_ctx = NULL;
}
if (dec_ctx)
{
#ifdef WITH_ISAL_CRYPTO
free(dec_ctx);
#else
EVP_CIPHER_CTX_free(dec_ctx);
#endif
dec_ctx = NULL;
}
if (ssl_cli)
if (hs)
{
SSL_free(ssl_cli);
ssl_cli = NULL;
write_to_ssl = NULL;
read_from_ssl = NULL;
delete hs;
hs = NULL;
}
if (ssl_out_buf)
if (hs_result.peer_cert)
{
free(ssl_out_buf);
ssl_out_buf = NULL;
X509_free(hs_result.peer_cert);
hs_result.peer_cert = NULL;
}
#endif
}
+1 -1
View File
@@ -1049,7 +1049,7 @@ static int coroutine_fn vitastor_co_block_status(BlockDriverState *bs,
{
// Get larger allocated extents, possibly with false positives
uint64_t bmp_pos = (offset-task.offset) / task.bitmap_granularity;
uint64_t bmp_end = (offset+bytes-task.offset) / task.bitmap_granularity - bmp_pos;
uint64_t bmp_end = (offset+bytes-task.offset) / task.bitmap_granularity;
while (bmp_pos < bmp_end)
{
if (!(bmp_pos & 7) && bmp_end >= bmp_pos+8)
+1 -1
View File
@@ -282,7 +282,7 @@ help:
exit(1);
}
}
const bool writeback = cli->get_immediate_commit(inode);
const bool writeback = !cli->get_immediate_commit(inode);
auto pool_it = cli->st_cli.pool_config.find(INODE_POOL(inode ? inode : watch->cfg.num));
if (pool_it == cli->st_cli.pool_config.end())
{
+1 -1
View File
@@ -6,7 +6,7 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: Vitastor
Description: Vitastor client library
Version: 3.0.9
Version: 3.0.12
Libs: -L${libdir} -lvitastor_client
Cflags: -I${includedir}
+1
View File
@@ -36,6 +36,7 @@ add_library(vitastor_cli STATIC
cli_pool_ls.cpp
cli_pool_modify.cpp
cli_pool_rm.cpp
cli_raw_ls.cpp
cli_serve.cpp
cli_status.cpp
cli_user_ls.cpp
+13 -3
View File
@@ -144,6 +144,11 @@ static const char* help_text =
" --min-offset, --max-offset\n"
" Restrict listing to specific offsets inside inodes.\n"
"\n"
"vitastor-cli raw-ls [OPTIONS]\n"
" Find object(s) in the cluster using raw secondary listing operations. Options:\n"
" [--min_inode NUM] [--max_inode NUM] [--offset NUM] [--pg_num NUM] [--pg_count COUNT]\n"
" [--pg_stripe_size NUM] [--osds 1,2,3,...]\n"
"\n"
"vitastor-cli fix [--objects <objects>] [--bad-osds <osds>] [--part <part>] [--check no]\n"
" Fix inconsistent objects in the cluster by deleting some copies.\n"
" --objects <objects>\n"
@@ -259,9 +264,9 @@ static const char* help_text =
" Start HTTP server able to handle CLI commands over a REST API. Options:\n"
" --bind_address ADDR Specify server IP address or addresses, separated by space. Default is 127.0.0.1.\n"
" --port 8080 Specify server port.\n"
" --ssl_cert FILE Path to server SSL certificate file (PEM format).\n"
" --ssl_key FILE Path to server SSL private key file.\n"
" --ssl_ca FILE Path to file with SSL CA certificates used to validate client connections.\n"
" --server_cert FILE Path to server TLS certificate file (PEM format).\n"
" --server_key FILE Path to server TLS private key file.\n"
" --client_ca FILE Path to file with TLS CA certificates used to validate client connections.\n"
"\n"
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
"\n"
@@ -509,6 +514,11 @@ std::function<bool(cli_result_t &)> cli_tool_t::start(json11::Json::object cfg,
// Describe unclean objects
action_cb = start_describe(cfg);
}
else if (cmd[0] == "raw-ls")
{
// Run raw listings
action_cb = start_raw_ls(cfg);
}
else if (cmd[0] == "fix")
{
// Fix inconsistent objects (by deleting some copies)
+3 -7
View File
@@ -27,12 +27,7 @@ struct cli_result_t
json11::Json data;
};
struct cli_user_t
{
std::string name;
std::string type;
std::set<std::string> groups;
};
struct user_info_t;
class cli_tool_t
{
@@ -45,7 +40,7 @@ public:
bool is_command_line = false;
bool color = false;
std::unique_ptr<cli_user_t> user; // for http mode
std::shared_ptr<user_info_t> user; // for http mode
ring_loop_t *ringloop = NULL;
epoll_manager_t *epmgr = NULL;
@@ -78,6 +73,7 @@ public:
std::function<bool(cli_result_t &)> start_fix(json11::Json);
std::function<bool(cli_result_t &)> start_flatten(json11::Json);
std::function<bool(cli_result_t &)> start_ls(json11::Json);
std::function<bool(cli_result_t &)> start_raw_ls(json11::Json cfg);
std::function<bool(cli_result_t &)> start_merge(json11::Json);
std::function<bool(cli_result_t &)> start_modify(json11::Json);
std::function<bool(cli_result_t &)> start_modify_osd(json11::Json);
+1 -1
View File
@@ -9,7 +9,7 @@
bool cli_tool_t::check_image_perm(const inode_config_t & cfg, bool write)
{
return !user ||
user->type == "admin" ||
user->type == user_type_t::ADMIN ||
user->name == cfg.owner ||
cfg.owner_group != "" && user->groups.find(cfg.owner_group) != user->groups.end() ||
!write && cfg.reader_group != "" && user->groups.find(cfg.reader_group) != user->groups.end();
+1 -5
View File
@@ -1,9 +1,7 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#ifdef WITH_OPENSSL
#include <openssl/rand.h>
#endif
#include <ctype.h>
#include "cli.h"
@@ -123,7 +121,7 @@ struct image_creator_t
bool check_pool_permission()
{
if (!parent->user || parent->user->type == "admin")
if (!parent->user || parent->user->type == user_type_t::ADMIN)
{
return true;
}
@@ -628,14 +626,12 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_create(json11::Json cfg)
if (!cfg["enc_key"].is_null())
{
image_creator->set_key = true;
#ifdef WITH_OPENSSL
if (image_creator->enc_key == "random")
{
uint8_t newkey[64];
RAND_bytes(newkey, 64);
image_creator->enc_key = tohexstr(newkey, 64);
}
#endif
else
{
image_creator->enc_key = cfg["enc_key"].string_value();
+1 -1
View File
@@ -147,7 +147,7 @@ resume_1:
inode_t inode_num = INODE_WITH_POOL(pool_id, only_inode_num);
uint64_t used_size = kv.value["raw_used"].uint64_value();
auto stat_it = stats.find(inode_num);
if (parent->user && parent->user->type != "admin" && stat_it == stats.end())
if (parent->user && parent->user->type != user_type_t::ADMIN && stat_it == stats.end())
{
continue;
}
+5 -1
View File
@@ -95,7 +95,11 @@ struct image_changer_t
(!set_readonly || cfg.readonly) &&
(!set_deleted || cfg.deleted == new_deleted) &&
(!new_size && !force_size || cfg.size == new_size || cfg.size >= new_size && inc_size) &&
(new_name == "" || new_name == image_name))
(new_name == "" || new_name == image_name) &&
(!new_owner.is_string() || new_owner.string_value() == cfg.owner) &&
(!new_owner_group.is_string() || new_owner_group.string_value() == cfg.owner_group) &&
(!new_reader_group.is_string() || new_reader_group.string_value() == cfg.reader_group) &&
!set_key)
{
result = (cli_result_t){ .err = 0, .text = "No change", .data = parent->format_image(cfg) };
state = 100;
+1 -1
View File
@@ -31,7 +31,7 @@ struct cli_modify_user_t
goto resume_1;
else if (state == 2)
goto resume_2;
if (user_type != "client" && user_type != "admin" && user_type != "mon" && user_type != "osd")
if (user_type != "client" && user_type != "admin")
{
result = (cli_result_t){ .err = EINVAL, .text = "Unknown user type: "+user_type };
state = 100;
+212
View File
@@ -0,0 +1,212 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include "cli_fix.h"
#include "cluster_client.h"
#include "pg_states.h"
#include "str_util.h"
#include "json_util.h"
std::vector<uint64_t> parse_uint64_list(json11::Json val);
// Find object(s) in the cluster using raw secondary listing operations.
// Only for troubleshooting.
struct cli_raw_ls_t
{
json11::Json cfg;
pool_id_t pool_id = 0;
pg_num_t pg_num = 0;
pg_num_t pg_count = 0;
uint32_t pg_stripe_size = 0;
std::set<uint64_t> osds;
std::vector<osd_num_t> osd_list;
uint64_t min_inode = 0, max_inode = 0, min_offset = 0, max_offset = 0;
bool offset_set = false;
cli_tool_t *parent = NULL;
int state = 0;
cli_result_t result;
bool first = true;
size_t osd_pos = 0;
bool is_done()
{
return state == 100;
}
void loop()
{
if (state == 1)
goto resume_1;
if (state == 100)
return;
pool_id = cfg["pool_id"].uint64_value();
pg_num = (pg_num_t)cfg["pg_num"].uint64_value();
pg_count = (pg_num_t)cfg["pg_count"].uint64_value();
pg_stripe_size = cfg["pg_stripe_size"].uint64_value();
if (!pool_id)
{
result = (cli_result_t){ .err = EINVAL, .text = "Pool ID is required" };
state = 100;
return;
}
if (cfg["min_inode"].is_null() && cfg["max_inode"].is_null())
{
min_inode = INODE_WITH_POOL(cfg["pool_id"].uint64_value(), 0);
max_inode = INODE_WITH_POOL(cfg["pool_id"].uint64_value(), UINT64_MAX);
}
else
{
min_inode = INODE_WITH_POOL(cfg["pool_id"].uint64_value(), stoull_full(cfg["min_inode"].as_string()));
max_inode = INODE_WITH_POOL(cfg["pool_id"].uint64_value(), stoull_full(cfg["max_inode"].as_string()));
}
if (cfg["offset"].is_null())
{
if (!pg_num)
{
result = (cli_result_t){ .err = EINVAL, .text = "PG number is required if offset is not specified" };
state = 100;
return;
}
min_offset = 0;
max_offset = UINT64_MAX;
}
else
{
min_offset = max_offset = stoull_full(cfg["offset"].as_string());
offset_set = true;
}
for (auto osd_num: parse_uint64_list(cfg["osds"]))
{
osds.insert(osd_num);
}
if (!pg_count || !pg_stripe_size || !osds.size())
{
auto pool_it = parent->cli->st_cli.pool_config.find(pool_id);
if (pool_it == parent->cli->st_cli.pool_config.end())
{
result = (cli_result_t){ .err = EINVAL, .text = "pg_count, pg_stripe_size and osds are required if the pool does not exist" };
state = 100;
return;
}
if (!pg_count)
{
pg_count = pool_it->second.real_pg_count;
}
if (!pg_stripe_size)
{
pg_stripe_size = pool_it->second.pg_stripe_size;
}
if (!osds.size())
{
for (auto & pgp: pool_it->second.pg_config)
{
for (osd_num_t pg_osd: pgp.second.target_set)
osds.insert(pg_osd);
for (osd_num_t pg_osd: pgp.second.all_peers)
osds.insert(pg_osd);
for (auto & hist_item: pgp.second.target_history)
for (auto pg_osd: hist_item)
osds.insert(pg_osd);
}
osds.erase(0);
}
}
if (offset_set)
{
min_offset = (min_offset / pg_stripe_size) * pg_stripe_size;
max_offset = ((max_offset / pg_stripe_size) * pg_stripe_size) | STRIPE_MASK;
pg_num = (min_offset/pg_stripe_size) % pg_count + 1; // like map_to_pg()
fprintf(stderr, "Selected PG %u\n", pg_num);
}
parent->cli->init_msgr();
osd_list = std::vector<osd_num_t>(osds.begin(), osds.end());
osd_pos = 0;
state = 1;
resume_1:
for (; osd_pos < osd_list.size() && parent->waiting < parent->parallel_osds; osd_pos++)
{
uint64_t osd_num = osd_list[osd_pos];
if (parent->cli->st_cli.peer_states[osd_num].is_null())
{
fprintf(stderr, "OSD %ju is unavailable, skipping\n", osd_num);
continue;
}
osd_op_t *op = new osd_op_t;
op->req = (osd_any_op_t){
.sec_list = {
.header = {
.magic = SECONDARY_OSD_OP_MAGIC,
.opcode = OSD_OP_SEC_LIST,
},
.list_pg = pg_num,
.pg_count = pg_count,
.pg_stripe_size = pg_stripe_size,
.min_inode = min_inode,
.max_inode = max_inode,
.min_stripe = min_offset,
.max_stripe = max_offset,
},
};
op->callback = [this, osd_num](osd_op_t *op)
{
if (op->reply.hdr.retval < 0)
{
fprintf(stderr, "OSD %ju listing failed: retval=%jd\n", osd_num, op->reply.hdr.retval);
}
else
{
for (uint64_t i = 0; i < op->reply.hdr.retval; i++)
{
auto & ov = ((obj_ver_id*)op->buf)[i];
if (parent->json_output)
{
printf("%s{\"osd\":%ju,\"inode\":\"0x%jx\",\"stripe\":\"0x%jx\",\"version\":%ju,\"stable\":%s}",
first ? "" : ",\n", osd_num, ov.oid.inode, ov.oid.stripe, ov.version,
i < op->reply.sec_list.stable_count ? "true" : "false");
first = false;
}
else
{
printf("OSD %ju - %jx:%jx v%ju%s\n",
osd_num, ov.oid.inode, ov.oid.stripe, ov.version,
i < op->reply.sec_list.stable_count ? " stable" : "");
}
}
}
parent->waiting--;
loop();
delete op;
};
parent->waiting++;
parent->cli->execute_raw(osd_num, op);
}
if (parent->waiting > 0)
{
return;
}
if (!first)
{
printf("\n");
}
state = 100;
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_raw_ls(json11::Json cfg)
{
auto raw_ls = new cli_raw_ls_t();
raw_ls->parent = this;
raw_ls->cfg = cfg;
return [raw_ls](cli_result_t & result)
{
raw_ls->loop();
if (raw_ls->is_done())
{
result = raw_ls->result;
delete raw_ls;
return true;
}
return false;
};
}
+33 -25
View File
@@ -76,6 +76,7 @@ struct cli_serve_t
int port = 0;
int listen_backlog = 0;
bool ssl = false;
bool use_auth = false;
std::vector<int> listen_fds;
http_context_t *http_ctx = NULL;
std::set<cli_serve_conn_t*> connections;
@@ -111,19 +112,34 @@ struct cli_serve_t
listen_backlog = options["listen_backlog"].uint64_value();
if (!listen_backlog)
listen_backlog = 128;
ssl = json_is_true(options["ssl"]);
if (ssl)
{
std::string ssl_cert = options["ssl_cert"].string_value();
std::string ssl_key = options["ssl_key"].string_value();
std::string ssl_ca = options["ssl_ca"].string_value();
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, ssl_cert, ssl_key, ssl_ca, ssl_ca != "", error);
if (error != "")
std::string tls_cert = (parent->cli->config.find("server_cert") != parent->cli->config.end()
? parent->cli->config["server_cert"].string_value() : "");
std::string tls_key = (parent->cli->config.find("server_key") != parent->cli->config.end()
? parent->cli->config["server_key"].string_value() : "");
std::string tls_ca = (parent->cli->config.find("client_ca") != parent->cli->config.end()
? parent->cli->config["client_ca"].string_value() : "");
if (tls_cert != "" || tls_key != "" || tls_ca != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
ssl = true;
if (tls_cert == "" || tls_key == "")
{
result = (cli_result_t){ .err = EINVAL, .text = "server_cert and server_key are required to serve HTTPS" };
state = 100;
return;
}
// use_auth is enabled by default when client_ca is set
use_auth = (parent->cli->config["use_auth"].is_null()
? (tls_ca != "")
: json_is_true(parent->cli->config["use_auth"]));
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, tls_cert, tls_key, tls_ca, tls_ca != "", error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
}
for (auto & bind_address: bind_addresses)
@@ -344,17 +360,9 @@ struct cli_serve_t
conn->request_path = std::move(req_line[1]);
conn->request_body = std::move(msg->body);
conn->response_type = "";
if (parent->cli->st_cli.use_auth)
if (use_auth)
{
auto user = std::make_unique<cli_user_t>();
user->name = msg->headers["_tls_common_name"];
auto user_it = parent->cli->st_cli.user_info.find(user->name);
auto userinfo = user_it == parent->cli->st_cli.user_info.end() ? user_it->second : json11::Json();
user->type = user->name == "root" ? "admin" : userinfo["type"].string_value();
for (auto & gr: userinfo["groups"].array_items())
{
user->groups.insert(gr.string_value());
}
conn->p->user = parent->cli->st_cli.get_user(msg->headers["_tls_common_name"]);
}
auto ctype = msg->headers["content-type"];
if (conn->request_method != "GET" && conn->request_method != "POST")
@@ -383,10 +391,10 @@ struct cli_serve_t
{
conn->response_type = "application/json";
conn->result = { .text = openapi_description };
if (parent->cli->st_cli.use_auth)
if (use_auth)
{
// Filter available paths by privileges
if (conn->p->user->type == "client")
if (conn->p->user->type == user_type_t::CLIENT)
{
std::string error;
auto openapi = json11::Json::parse(openapi_description, error).object_items();
@@ -403,7 +411,7 @@ struct cli_serve_t
conn->response_type = "application/json";
conn->result = { .text = json11::Json(openapi).dump() };
}
else if (conn->p->user->type != "admin")
else if (conn->p->user->type != user_type_t::ADMIN)
{
conn->response_type = "";
conn->result = { .err = EACCES, .text = "Access denied" };
@@ -418,7 +426,7 @@ struct cli_serve_t
{
conn->result = { .err = ENOSYS, .text = "method /"+uri[0]+" only allows POST requests" };
}
else if (parent->cli->st_cli.use_auth && conn->p->user->type == "client" && !cmd_it->second.allow_client)
else if (use_auth && conn->p->user->type == user_type_t::CLIENT && !cmd_it->second.allow_client)
{
conn->result = { .err = EACCES, .text = "Access denied" };
}
+1 -1
View File
@@ -946,7 +946,7 @@
"type": "object",
"properties": {
"name": { "type": "string", "description": "User name" },
"type": { "type": "string", "enum": [ "osd", "mon", "admin", "client" ], "description": "User type" },
"type": { "type": "string", "enum": [ "admin", "client" ], "description": "User type" },
"groups": { "type": "array", "items": { "type": "string" }, "description": "User group names" }
}
},
-1
View File
@@ -11,7 +11,6 @@ add_executable(vitastor-disk
../blockstore/blockstore_disk.cpp ../blockstore/blockstore_heap.cpp ../blockstore/multilist.cpp
)
target_link_libraries(vitastor-disk
tcmalloc_minimal
${LIBURING_LIBRARIES}
${ISAL_LIBRARIES}
)
+3
View File
@@ -427,6 +427,9 @@ int main(int argc, char *argv[])
self.dsk.open_journal();
self.dsk.calc_lengths();
self.dsk.close_all();
self.new_meta_device = self.dsk.meta_device;
self.new_meta_offset = self.dsk.meta_offset;
self.new_meta_len = self.dsk.meta_area_size;
}
std::string json_err;
json11::Json meta = json11::Json::parse(read_all_fd(0), json_err);
-3
View File
@@ -52,7 +52,6 @@ struct disk_tool_t
bool all = false, json = false, now = false;
bool dump_with_blocks = false, dump_with_data = false;
bool dump_as_old = false;
bool skip_obsolete = false;
int log_level = 1;
double meta_reserve_multiple = 2;
uint64_t meta_reserve_min_size = (uint64_t)1024*1024*1024;
@@ -138,8 +137,6 @@ struct disk_tool_t
int resize_write_new_journal();
void remap_big_write(blockstore_heap_t *heap, heap_entry_t *wr);
void remap_small_write(blockstore_heap_t *heap, heap_entry_t *wr);
void fill_old_clean_entry(blockstore_heap_t *heap, heap_entry_t *big_wr);
void fill_old_journal_entry(blockstore_heap_t *heap, heap_entry_t *wr);
int resize_rebuild_meta();
int resize_write_new_meta();
void free_new_meta();
+2 -1
View File
@@ -590,6 +590,7 @@ int disk_tool_t::write_json_meta(json11::Json meta)
int disk_tool_t::write_json_heap(json11::Json meta, json11::Json journal)
{
assert(new_meta_len >= sizeof(blockstore_meta_header_v3_t));
new_meta_buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, new_meta_len);
memset(new_meta_buf, 0, new_meta_len);
new_meta_hdr = (blockstore_meta_header_v3_t *)new_meta_buf;
@@ -607,9 +608,9 @@ int disk_tool_t::write_json_heap(json11::Json meta, json11::Json journal)
: (meta["data_csum_type"].string_value() == "crc32c"
? BLOCKSTORE_CSUM_CRC32C
: BLOCKSTORE_CSUM_NONE);
new_meta_hdr->meta_area_size = new_meta_len;
new_meta_hdr->csum_block_size = meta["csum_block_size"].uint64_value();
new_meta_hdr->header_csum = crc32c(0, new_meta_hdr, sizeof(blockstore_meta_header_v3_t));
new_meta_hdr->meta_area_size = new_meta_len;
new_clean_entry_bitmap_size = (new_meta_hdr->data_block_size / new_meta_hdr->bitmap_granularity + 7) / 8;
new_clean_entry_size = 0;
new_entries_per_block = 0;
+19 -136
View File
@@ -111,7 +111,7 @@ int disk_tool_t::raw_resize()
fprintf(stderr, "Done\n");
ret:
free_new_meta();
return 0;
return r;
}
int disk_tool_t::resize_parse_params()
@@ -154,9 +154,6 @@ int disk_tool_t::resize_parse_params()
? parse_size(options["new_journal_offset"]) : dsk.journal_offset;
new_journal_len = options.find("new_journal_len") != options.end()
? parse_size(options["new_journal_len"]) : dsk.journal_len;
new_meta_format = options.find("new_meta_format") != options.end()
? stoull_full(options["new_meta_format"]) : 0;
skip_obsolete = options.find("skip_obsolete") != options.end();
if (new_data_len+new_data_offset > dsk.data_device_size)
new_data_len = dsk.data_device_size-new_data_offset;
if (new_meta_device == dsk.data_device && new_data_offset < new_meta_offset &&
@@ -205,10 +202,7 @@ void disk_tool_t::resize_init(blockstore_meta_header_v3_t *hdr)
{
dsk.meta_format = hdr->version;
}
if (new_meta_format == 0)
{
new_meta_format = hdr && hdr->version == BLOCKSTORE_META_FORMAT_HEAP ? BLOCKSTORE_META_FORMAT_HEAP : BLOCKSTORE_META_FORMAT_V2;
}
new_meta_format = hdr && hdr->version == BLOCKSTORE_META_FORMAT_HEAP ? BLOCKSTORE_META_FORMAT_HEAP : BLOCKSTORE_META_FORMAT_V2;
dsk.calc_lengths();
if (((new_data_offset-dsk.data_offset) % dsk.data_block_size))
{
@@ -563,66 +557,6 @@ void disk_tool_t::remap_small_write(blockstore_heap_t *heap, heap_entry_t *wr)
}
}
void disk_tool_t::fill_old_clean_entry(blockstore_heap_t *heap, heap_entry_t *big_wr)
{
uint64_t block_num = big_wr->big().block_num;
clean_disk_entry *new_entry = (clean_disk_entry*)(new_meta_buf + dsk.meta_block_size +
dsk.meta_block_size*(block_num / new_entries_per_block) +
new_clean_entry_size*(block_num % new_entries_per_block));
new_entry->oid = (object_id){ .inode = big_wr->inode, .stripe = big_wr->stripe };
new_entry->version = big_wr->version;
memcpy(new_entry->bitmap, big_wr->get_ext_bitmap(heap), new_clean_entry_bitmap_size);
memcpy(new_entry->bitmap + new_clean_entry_bitmap_size, big_wr->get_int_bitmap(heap), new_clean_entry_bitmap_size);
memcpy(new_entry->bitmap + 2*new_clean_entry_bitmap_size, big_wr->get_checksums(heap), new_data_csum_size);
uint32_t *new_entry_csum = (uint32_t*)(((uint8_t*)new_entry) + new_clean_entry_size - 4);
*new_entry_csum = crc32c(0, new_entry, new_clean_entry_size - 4);
}
void disk_tool_t::fill_old_journal_entry(blockstore_heap_t *heap, heap_entry_t *wr)
{
assert(wr->type() == BS_HEAP_SMALL_WRITE ||
wr->type() == BS_HEAP_BIG_WRITE ||
wr->type() == BS_HEAP_BIG_INTENT);
uint32_t je_size = ((wr->entry_type & BS_HEAP_TYPE) == BS_HEAP_SMALL_WRITE
? sizeof(journal_entry_small_write) + dsk.dirty_dyn_size(wr->small().offset, wr->small().len)
: sizeof(journal_entry_big_write) + dsk.dirty_dyn_size(0, dsk.data_block_size));
choose_journal_block(je_size);
journal_entry *je = (journal_entry*)(new_journal_ptr + new_journal_in_pos);
je->magic = JOURNAL_MAGIC;
je->type = (wr->entry_type & BS_HEAP_STABLE) ? JE_SMALL_WRITE_INSTANT : JE_SMALL_WRITE;
je->size = je_size;
je->crc32_prev = new_crc32_prev;
je->small_write.oid = (object_id){ .inode = wr->inode, .stripe = wr->stripe };
je->small_write.version = wr->version;
if (wr->type() == BS_HEAP_SMALL_WRITE)
{
je->small_write.offset = wr->small().offset;
je->small_write.len = wr->small().len;
je->small_write.data_offset = new_journal_data-new_journal_buf;
if (je->small_write.data_offset + je->small_write.len > new_journal_len)
{
fprintf(stderr, "Error: live entries don't fit to the new journal\n");
exit(1);
}
memcpy(new_journal_data, buffer_area+wr->small().location, je->small_write.len);
new_journal_data += je->small_write.len;
if (dsk.data_csum_type == 0 && wr->get_checksum(heap))
je->small_write.crc32_data = *wr->get_checksum(heap);
}
else
{
je->big_write.location = wr->big_location(heap);
}
memcpy((uint8_t*)je + je->size, wr->get_ext_bitmap(heap), new_clean_entry_bitmap_size);
if (dsk.data_csum_type != 0 && wr->get_checksums(heap))
{
memcpy((uint8_t*)je + je->size + new_clean_entry_bitmap_size, wr->get_checksums(heap), heap->get_csum_size(wr));
}
je->crc32 = je_crc32(je);
new_journal_in_pos += je->size;
new_crc32_prev = je->crc32;
}
int disk_tool_t::resize_rebuild_meta()
{
new_meta_buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, new_meta_len);
@@ -630,13 +564,12 @@ int disk_tool_t::resize_rebuild_meta()
new_meta_hdr = (blockstore_meta_header_v3_t *)new_meta_buf;
uint64_t new_meta_pos = dsk.meta_block_size;
uint64_t next_lsn = 0;
std::vector<heap_entry_t*> writes;
int r = process_meta(
[&](blockstore_meta_header_v3_t *hdr)
{
new_meta_hdr->zero = 0;
new_meta_hdr->magic = BLOCKSTORE_META_MAGIC_V1;
new_meta_hdr->version = new_meta_format == 0 ? BLOCKSTORE_META_FORMAT_HEAP : new_meta_format;
new_meta_hdr->version = new_meta_format;
new_meta_hdr->meta_block_size = dsk.meta_block_size;
new_meta_hdr->data_block_size = dsk.data_block_size;
new_meta_hdr->bitmap_granularity = dsk.bitmap_granularity ? dsk.bitmap_granularity : 4096;
@@ -654,18 +587,16 @@ int disk_tool_t::resize_rebuild_meta()
},
[&](blockstore_heap_t *heap, heap_entry_t *obj, uint32_t meta_block_num)
{
assert(new_meta_format == BLOCKSTORE_META_FORMAT_HEAP);
if (!obj)
{
// Finish
if (new_meta_format == BLOCKSTORE_META_FORMAT_HEAP)
heap->fill_block_empty_space(new_meta_buf, new_meta_pos);
new_meta_pos = (new_meta_pos/dsk.meta_block_size + 1) * dsk.meta_block_size;
while (new_meta_pos < new_meta_len)
{
heap->fill_block_empty_space(new_meta_buf, new_meta_pos);
new_meta_pos = (new_meta_pos/dsk.meta_block_size + 1) * dsk.meta_block_size;
while (new_meta_pos < new_meta_len)
{
heap->fill_block_empty_space(new_meta_buf, new_meta_pos);
new_meta_pos += dsk.meta_block_size;
}
new_meta_pos += dsk.meta_block_size;
}
return;
}
@@ -679,72 +610,24 @@ int disk_tool_t::resize_rebuild_meta()
{
remap_small_write(heap, wr);
}
else if (wr->type() != BS_HEAP_DELETE && new_meta_format != BLOCKSTORE_META_FORMAT_HEAP)
// New -> New
if ((new_meta_pos % dsk.meta_block_size) + wr->size > dsk.meta_block_size)
{
fprintf(stderr, "Object %jx:%jx can't be converted to the old format because it contains an entry of type 0x%x%s\n",
wr->inode, wr->stripe, wr->entry_type,
(wr->type() == BS_HEAP_INTENT_WRITE ? " (intent_write)" : ""));
exit(1);
}
if (new_meta_format == BLOCKSTORE_META_FORMAT_HEAP)
{
// New -> New
if ((new_meta_pos % dsk.meta_block_size) + wr->size > dsk.meta_block_size)
heap->fill_block_empty_space(new_meta_buf, new_meta_pos);
new_meta_pos = (new_meta_pos/dsk.meta_block_size + 1) * dsk.meta_block_size;
if (new_meta_pos >= new_meta_len)
{
heap->fill_block_empty_space(new_meta_buf, new_meta_pos);
new_meta_pos = (new_meta_pos/dsk.meta_block_size + 1) * dsk.meta_block_size;
if (new_meta_pos >= new_meta_len)
{
fprintf(stderr, "New metadata doesn't fit into the provided area\n");
exit(1);
}
}
memcpy(new_meta_buf + new_meta_pos, wr, wr->size);
new_meta_pos += wr->size;
if (skip_obsolete && wr->type() == BS_HEAP_BIG_WRITE && stable)
{
// Skip older writes
return false;
}
}
else
{
// New -> Old
if (wr->type() == BS_HEAP_DELETE && stable)
{
// Object is deleted, skip it
return false;
}
if (wr->type() == BS_HEAP_BIG_WRITE && stable)
{
fill_old_clean_entry(heap, wr);
return false;
}
else
{
writes.push_back(wr);
fprintf(stderr, "New metadata doesn't fit into the provided area\n");
exit(1);
}
}
memcpy(new_meta_buf + new_meta_pos, wr, wr->size);
new_meta_pos += wr->size;
return true;
};
if (new_meta_format != BLOCKSTORE_META_FORMAT_HEAP || skip_obsolete)
for (auto wr = obj; wr; wr = heap->prev(wr))
{
heap->iterate_with_stable(obj, obj->lsn, handle_write);
}
else
{
for (auto wr = obj; wr; wr = heap->prev(wr))
{
handle_write(wr, false);
}
}
if (writes.size())
{
for (size_t i = writes.size(); i > 0; i--)
{
fill_old_journal_entry(heap, writes[i-1]);
}
writes.clear();
handle_write(wr, false);
}
},
[&](uint64_t block_num, clean_disk_entry *entry, uint8_t *bitmap)
+2 -1
View File
@@ -121,6 +121,7 @@ static const char* help_text =
" --logfile <FILE> log to the specified file\n"
" --enforce 1 enforce permissions at the server side (default is disabled)\n"
" --foreground 1 stay in foreground, do not daemonize\n"
" --trace trace all NFS requests\n"
"\n"
"NFS proxy is stateless if you use immediate_commit=all in your cluster and if\n"
"you do not use client_enable_writeback=true, so you can freely use multiple\n"
@@ -158,7 +159,7 @@ json11::Json::object nfs_proxy_t::parse_args(int narg, const char *args[])
{
const char *opt = args[i]+2;
cfg[str_replace(opt, "-", "_")] = !strcmp(opt, "json") || !strcmp(opt, "block") ||
!strcmp(opt, "dry-run") || !strcmp(opt, "recalc-stats") ||
!strcmp(opt, "dry-run") || !strcmp(opt, "recalc-stats") || !strcmp(opt, "trace") ||
!strcmp(opt, "include-empty") || !strcmp(opt, "no-rm") || i == narg-1 ? "1" : args[++i];
}
else
+5 -5
View File
@@ -586,8 +586,8 @@ send_again:
}
// Estimate reply WR count, create WR and SGE arrays
xdr_write_chunk *reply_chunk = rop->in_rdma_msg.rdma_body.rdma_msg.rdma_reply;
int reply_chunk_wr_count = (reply_chunk ? reply_chunk->target.target_len : 0);
uint32_t wr_count = 1 + (chunk_iov ? 1 : 0) + (reply_chunk ? reply_chunk_wr_count : 0);
uint32_t reply_chunk_wr_count = (reply_chunk ? reply_chunk->target.target_len : 0);
uint32_t wr_count = 1 + (chunk_iov ? 1 : 0) + reply_chunk_wr_count;
if (wr_count > ctx->max_send_wr)
{
fprintf(stderr, "Reply fragmentation (%u) exceeds max_send_wr (%u), sending ERR_CHUNK\n", wr_count, ctx->max_send_wr);
@@ -622,7 +622,7 @@ chunk_error:
{
size_t reply_chunk_len = 0;
size_t left = msg_size;
for (uint32_t i = 0; i < reply_chunk->target.target_len; i++)
for (uint32_t i = 0; i < reply_chunk_wr_count; i++)
{
reply_chunk_len += reply_chunk->target.target_val[i].length;
if (reply_chunk->target.target_val[i].length > left)
@@ -653,7 +653,7 @@ chunk_error:
}
ibv_sge sges[wr_count];
ibv_send_wr wrs[wr_count];
int wr_pos = 0;
uint32_t wr_pos = 0;
// Use a buffer from rdma_malloc for the reply
assert(!rop->buffer);
rop->buffer = rdma_malloc_alloc(conn_dev->alloc, hdr_size+msg_size);
@@ -685,7 +685,7 @@ chunk_error:
if (reply_chunk)
{
size_t pos = hdr_size;
for (uint32_t i = 0; i < reply_chunk->target.target_len && pos < msg_size; i++)
for (uint32_t i = 0; i < reply_chunk_wr_count && pos < msg_size; i++)
{
uint32_t len = (reply_chunk->target.target_val[i].length < msg_size-pos
? reply_chunk->target.target_val[i].length : msg_size-pos);
+2 -3
View File
@@ -20,20 +20,19 @@ target_link_libraries(vitastor-osd
# osd_rmw_test
add_executable(osd_rmw_test EXCLUDE_FROM_ALL osd_rmw_test.cpp ../util/allocator.cpp)
target_link_libraries(osd_rmw_test Jerasure ${ISAL_LIBRARIES} tcmalloc_minimal)
target_link_libraries(osd_rmw_test Jerasure ${ISAL_LIBRARIES})
add_dependencies(build_tests osd_rmw_test)
add_test(NAME osd_rmw_test COMMAND osd_rmw_test)
if (ISAL_LIBRARIES)
add_executable(osd_rmw_test_je EXCLUDE_FROM_ALL osd_rmw_test.cpp ../util/allocator.cpp)
target_compile_definitions(osd_rmw_test_je PUBLIC -DNO_ISAL)
target_link_libraries(osd_rmw_test_je Jerasure tcmalloc_minimal)
target_link_libraries(osd_rmw_test_je Jerasure)
add_dependencies(build_tests osd_rmw_test_je)
add_test(NAME osd_rmw_test_jerasure COMMAND osd_rmw_test_je)
endif (ISAL_LIBRARIES)
# osd_peering_pg_test
add_executable(osd_peering_pg_test EXCLUDE_FROM_ALL osd_peering_pg_test.cpp osd_peering_pg.cpp)
target_link_libraries(osd_peering_pg_test tcmalloc_minimal)
add_dependencies(build_tests osd_peering_pg_test)
add_test(NAME osd_peering_pg_test COMMAND osd_peering_pg_test)
+17 -8
View File
@@ -14,23 +14,21 @@
#include "http_client.h"
#include "str_util.h"
#include "json_util.h"
#include "openssl_util.h"
osd_t::osd_t(const json11::Json & config, ring_loop_t *ringloop)
osd_t::osd_t(const json11::Json & config, ring_loop_i *ringloop, timerfd_manager_t *tfd)
{
zero_buffer_size = 1<<20;
zero_buffer = malloc_or_die(zero_buffer_size);
memset(zero_buffer, 0, zero_buffer_size);
this->ringloop = ringloop;
this->tfd = tfd;
this->cli_config = config.object_items();
this->file_config = msgr.read_config(this->cli_config);
parse_config(true);
epmgr = new epoll_manager_t(ringloop);
// FIXME: Use timerfd_interval based directly on io_uring
this->tfd = epmgr->tfd;
if (json_is_true(this->config["osd_memlock"]))
{
// Lock all OSD memory if requested
@@ -67,6 +65,11 @@ osd_t::osd_t(const json11::Json & config, ring_loop_t *ringloop)
msgr.repeer_pgs = [this](osd_num_t peer_osd) { repeer_pgs(peer_osd); };
msgr.break_pg_locks = [this](osd_num_t peer_osd) { break_pg_locks(peer_osd); };
msgr.check_config_hook = [this](osd_client_t *cl, json11::Json conf) { return check_peer_config(cl, conf); };
msgr.handshake_hook = [this](osd_client_t *cl)
{
if (!cl->hs_result.peer_is_osd)
cl->user_info = st_cli.get_user(openssl_get_cn(cl->hs_result.peer_cert));
};
msgr.init();
init_cluster();
@@ -99,7 +102,6 @@ osd_t::~osd_t()
}
ringloop->unregister_consumer(&consumer);
ringloop->unregister_consumer(&init_consumer);
delete epmgr;
if (bs)
delete bs;
#ifdef WITH_RDMACM
@@ -170,9 +172,13 @@ void osd_t::parse_config(bool init)
bs->parse_config(bs_cfg);
}
st_cli.parse_config(config);
msgr.parse_config(config);
msgr.parse_config(config, init);
if (init)
{
// use_auth is enabled by default when encryption is enabled
use_auth = (config["use_auth"].is_null()
? msgr.is_encryption_enabled()
: json_is_true(config["use_auth"]));
// Vital Blockstore parameters
bs_block_size = config["block_size"].uint64_value();
if (!bs_block_size)
@@ -181,6 +187,9 @@ void osd_t::parse_config(bool init)
if (!bs_bitmap_granularity)
bs_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
clean_entry_bitmap_size = bs_block_size / bs_bitmap_granularity / 8;
msgr.bs_block_size = bs_block_size;
msgr.clean_entry_bitmap_size = clean_entry_bitmap_size;
msgr.max_write_request_size = MAX_DATA_BLOCK_SIZE; // will be changed after pool config
// immediate_commit
if (config["immediate_commit"] == "all")
immediate_commit = IMMEDIATE_ALL;
@@ -399,7 +408,7 @@ void osd_t::bind_socket()
{
int listen_fd = create_and_bind_socket(bind_address, listening_port ? listening_port : bind_port, listen_backlog, &listening_port);
fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL, 0) | O_NONBLOCK);
epmgr->set_fd_handler(listen_fd, false, [this](int fd, int events)
tfd->set_fd_handler(listen_fd, false, [this](int fd, int events)
{
msgr.accept_connections(fd);
});
+8 -7
View File
@@ -19,7 +19,6 @@
#include "blockstore.h"
#include "ringloop.h"
#include "timerfd_manager.h"
#include "epoll_manager.h"
#include "osd_peering_pg.h"
#include "messenger.h"
#include "etcd_state_client.h"
@@ -30,6 +29,8 @@
#define OSD_RECOVERING 0x10
#define OSD_SCRUBBING 0x20
#define SELF_CLIENT 0
#define MAX_AUTOSYNC_INTERVAL 3600
#define DEFAULT_AUTOSYNC_INTERVAL 5
#define DEFAULT_AUTOSYNC_WRITES 128
@@ -155,6 +156,7 @@ class osd_t
etcd_state_client_t st_cli;
osd_messenger_t msgr;
bool use_auth = false;
int etcd_failed_attempts = 0;
std::string etcd_lease_id;
json11::Json self_state;
@@ -210,9 +212,8 @@ class osd_t
void *zero_buffer = NULL;
uint64_t zero_buffer_size = 0;
uint32_t bs_block_size, bs_bitmap_granularity, clean_entry_bitmap_size;
ring_loop_t *ringloop = NULL;
ring_loop_i *ringloop = NULL;
timerfd_manager_t *tfd = NULL;
epoll_manager_t *epmgr = NULL;
int listening_port = 0;
std::vector<std::string> bind_addresses;
@@ -352,8 +353,7 @@ class osd_t
std::function<int(pg_osd_set_t & new_set)> calc_set);
pg_osd_set_state_t *mark_object_corrupted(pg_t & pg, object_id oid, pg_osd_set_state_t *prev_object_state,
osd_rmw_stripe_t *stripes, bool ref);
pg_osd_set_state_t *mark_partial_write(pg_t & pg, object_id oid, pg_osd_set_state_t *prev_object_state,
osd_rmw_stripe_t *stripes, bool ref);
pg_osd_set_state_t *mark_partial_write(pg_t & pg, osd_op_t *cur_op);
void deref_object_state(pg_t & pg, pg_osd_set_state_t **object_state, bool deref);
bool remember_unstable_write(osd_op_t *cur_op, pg_t & pg, pg_osd_set_t & loc_set, int base_state);
void handle_primary_subop(osd_op_t *subop, osd_op_t *cur_op);
@@ -366,9 +366,10 @@ class osd_t
osd_rmw_stripe_t *stripes, const uint64_t* osd_set, osd_op_t *cur_op, int subop_idx, int zero_read);
void submit_primary_subop(osd_op_t *cur_op, osd_op_t *subop,
osd_rmw_stripe_t *si, bool wr, inode_t inode, uint64_t op_version);
bool submit_to_osd(osd_op_t *subop, osd_num_t osd_num);
void submit_primary_del_subops(osd_op_t *cur_op, uint64_t *cur_set, uint64_t set_size, pg_osd_set_t & loc_set);
void submit_primary_del_batch(osd_op_t *cur_op, obj_ver_osd_t *chunks_to_delete, int chunks_to_delete_count);
int submit_primary_sync_subops(osd_op_t *cur_op);
void submit_primary_sync_subops(osd_op_t *cur_op);
void submit_primary_stab_subops(osd_op_t *cur_op);
void submit_primary_rollback_subops(osd_op_t *cur_op, const uint64_t* osd_set);
@@ -392,7 +393,7 @@ class osd_t
}
public:
osd_t(const json11::Json & config, ring_loop_t *ringloop);
osd_t(const json11::Json & config, ring_loop_i *ringloop, timerfd_manager_t *tfd);
~osd_t();
void force_stop(int exitcode);
bool shutdown();
+16 -1
View File
@@ -428,6 +428,21 @@ void osd_t::on_change_pool_config_hook()
{
apply_pg_locks_localize_only();
}
msgr.max_write_request_size = 0;
for (auto & pc: st_cli.pool_config)
{
auto & pool_cfg = pc.second;
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint32_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
if (msgr.max_write_request_size < pg_block_size)
{
msgr.max_write_request_size = pg_block_size;
}
}
if (!msgr.max_write_request_size)
{
msgr.max_write_request_size = MAX_DATA_BLOCK_SIZE;
}
}
void osd_t::apply_pg_locks_localize_only()
@@ -729,7 +744,7 @@ void osd_t::apply_pg_count()
for (auto & pool_item: st_cli.pool_config)
{
auto & pool_cfg = pool_item.second;
if (pool_cfg.real_pg_count == 0)
if (pool_cfg.real_pg_count == 0 || !pool_cfg.exists)
{
continue;
}
+4 -11
View File
@@ -4,7 +4,8 @@
#include "osd.h"
#define FLUSH_BATCH 512
#define SELF_CLIENT 0
static_assert(FLUSH_BATCH <= MAX_SIMPLE_PAYLOAD_SIZE / sizeof(obj_ver_id));
void osd_t::submit_pg_flush_ops(pg_t & pg)
{
@@ -97,6 +98,7 @@ void osd_t::handle_flush_op(bool rollback, pool_id_t pool_id, pg_num_t pg_num, p
{
// Will repeer/stop this PG
msgr.stop_client(peer_it->second->client_id);
return;
}
}
}
@@ -213,17 +215,8 @@ bool osd_t::submit_flush_op(pool_id_t pool_id, pg_num_t pg_num, pg_flush_batch_t
handle_flush_op(op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK, pool_id, pg_num, fb, peer_osd, op->reply.hdr.retval);
delete op;
};
auto peer_it = msgr.osd_peers.find(peer_osd);
if (peer_it != msgr.osd_peers.end())
if (!submit_to_osd(op, peer_osd))
{
op->client_id = peer_it->second->client_id;
msgr.outbox_push(op);
}
else
{
// Fail it immediately
op->reply.hdr.retval = -EPIPE;
op->callback(op);
return false;
}
}
+4 -1
View File
@@ -1,6 +1,7 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include "epoll_manager.h"
#include "osd.h"
#include <sys/prctl.h>
@@ -63,13 +64,15 @@ int main(int narg, char *args[])
signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
ring_loop_t *ringloop = new ring_loop_t(RINGLOOP_DEFAULT_SIZE);
osd = new osd_t(config, ringloop);
epoll_manager_t *epmgr = new epoll_manager_t(ringloop);
osd = new osd_t(config, ringloop, epmgr->tfd);
while (1)
{
ringloop->loop();
ringloop->wait();
}
delete osd;
delete epmgr;
delete ringloop;
return 0;
}

Some files were not shown because too many files have changed in this diff Show More