mirror of https://github.com/sysown/proxysql
feature/aws-rds-monitor
v3.0_fix_ci-mysqlx-cache-and-soak
GH-Actions
v3.0
v3.0_fix_codecov-coverage-path-prefix
issue-1288-load-mysql-variables-feedback
ci/fix-pgsql-socket-g1-cache-key
v3.0_fix_ci-mysqlx-fetch-depth
cleanup/drop-unreachable-port-defaults
feature/pgsql-native-backend-protocol
fix/pgsql-unix-socket
ci-pgsql-socket-g1
v3.0_fix_coverage-gcov-prefix-strip
fix/pgsql-omit-port-zero
aws-rds-bg
fix/jemalloc-page-size-auto-detect
docs/passthrough-auth-spec
feature/ci-codecov-tap-all-groups-callers
fix/run-tests-backtick-leak
ci/zstd-level-15
feature/ci-codecov-tap-all-groups-callees
v3.0_partition-fairness
feature/ci-codecov-tap-legacy-g2
v3.0_partition-gate
fix/ci-cache-restore-path
v3.0_fix-stale-pause-until
feature/perf-improvements-test2
fix/kill-proxysqlgenai-build-flag
feat/passthrough-auth
ci-trigger-tolerate-cleanup-401
fix/5790-mariadb-collation-255
fix/parsersql-1.0.3-pg-set-fixes
issue_5639
pgsql_dns_cache
fix/5755-followup-typecast-digest-fixtures
v3.0_merge-5776-5784
v3.0_latency_consistency_improvement
fix/galera-g5-cluster-start
fix/ghcr-pull-retry
v3.0_cap_violation_5767
ci-mariadb10-galera-GH-Actions
ci/fix-gr-g5-cluster-start
ci-mysql84-gr-g1-g9-GH-Actions
perf/pull-ci-base-from-ghcr
fix/split-test-groups
fix/rename-set-parser-workflow
gh-actions/add-set-parser-algorithm-3-g1
fix/4760-advertise-lenenc-auth-capability
feature/mysqlx-stack-consolidated
feature/mysqlx-asan-coverage-docker-isolation
fix/ci-unit-tests-tsan-project-name
feature/mysqlx-tsan-v3-companion
feature/ci-builds-add-tsan-matrix
ci-reduce-polling-interval
feature/mysqlx-tsan-workflow-only
feature/mysqlx-ci-validation-workflows
feature/mysqlx-test-leak-cleanup
feature/mysqlx-behavioural-tap
feature/mysqlx-parity-cleanup
ci-g5-enable-cluster
feature/mysqlx-tls-passthrough
feature/mysqlx-asymmetric-tls
feature/mysqlx-state-machines
feature/build-tsan-plumbing
feature/mysqlx-observability-p0
fix/mysqlx-review-findings
v3.0-test-ci
ci/fix-mysql-apt-key-expired-v2
ci/fix-mysql-apt-key-expired
mariadb-rpl-helper
fix-fc-parsing
ci/fix-upload-artifact-eacces
ci/fix-cache-prune-permissions
ci/shrink-test-cache
ci/fix-tap-build-target
ci/gh-actions-readme-pointer
ci/fix-mysql84-infradb-label
ci/add-missing-group-reusables
infra-mysql57-binlog
feature/pgbouncer-compat
v3.0_pgsql_sslkeylog_5281
fix/5554-resolution-family-limitation
fix/3p-ci-error-handling
v3.0-5493
v3.0-ci260322_cluster
copilot/extract-server-selection-algorithm
copilot/extract-health-state-logic
copilot/extract-query-rule-matching-logic
copilot/extract-connection-pool-logic
v3.0-set_parser_v3
feature/arm-builds
release-notes-3.0.6-4.0.6-draft
v3.0.6-add-tap-test_stats_table_check
v2.7.3-test260221
v4.0-mcp-stats
copilot/uninstall-amazon-linux-2023
fix-prometheus-labels-test
tap-mcp-client
agent-skill-tap-test
v4.0-tsdb1
v3.0-fix_5256
gh-pages
feature/modern-docs
v4.0
v4.0-fix-vec-search
v4.0_rag_sys_prompt
v4.0_rag_mcp
v4.0-tsdb
feature/v4-docs-init
otel_system_libs
otel_clean
v3.0-5288
otel
otel_2
fix/postgresql-cluster-sync
v3.0-releate_notes_scripts_fixes
test_gh-actions_triggers
postgresql-digest-testing-improvement
v3.0_select_auto_commit
v3.0-5218
fix-5221
fix/5186-proxysql-stop-admin-crash
v3.0-4951
add-claude-github-actions-1763877527835
fix-rpm
v3.0-DS_crash
add-claude-github-actions-1763663272333
add-claude-github-actions-1763663091346
add-claude-github-actions-1763663091411
add-claude-github-actions-1763476725261
add-claude-github-actions-1763476725489
v3.0_optimizations_and_stability
v2.7.3.1
v3.0.3-upgrade_json
v3.0.sonar-cli
v3.0.sonar-config
otel-tracepoint
v3.0.2-merge-upgrade_deps-add_new_distros
v3.0.2-upgrade_deps
v3.0.2-add_new_distros
v3.0-add_more_testing_groups
v3.0-upgrade_prometheus-cpp
v3.0-upgrade_json
v3.0-upgrade_sqlite3
v3.0-upgrade_libmicrohttpd
v3.0-upgrade_curl
v3.0-add_centos10_builds
v3.0-add_fedora42_builds
v3.0_PG_PrepStmt
v3.0-sliced_groups
v3.0_auth_negotiation
v2.7
v2.7-fix_run_name
v3.0_4799_4827
v3.0-3687
v2.7-pmm_runtime_servers_metrics
v2.7-4839
v2.7-4841
v2.7-bump_version_to_2.7.3
2.6.6-4841
v2.x_pg_PrepStmtBase_240714
v3.0-4803-4817
v3.0-4803
v2.7-minorBugs
v3.0-privates
v2.x-logging_mem_2
v2.7_fix
v2.7_amd64_build_fix
v2.7-fix_aux_threads_ssl_leaks
v2.7-fix_ssl_params_leak
v2.7-rm_malloc_conf_on_version
v2.7_compression
v2.7-actions-add-3p-tests-parameter
none
v2.7-fix_hang_on_resume
v2.x-logging_mem
v2.7_servers_defaults
v2.7-mariadb_column_metadata_integrity_check
ssl_optimization
v2.7_reg_test_4716_single_semicolon
v2.7_issue_4707_threshold_resultset_size
v2.7_reg_test_4723_query_cache_stores_empty_result
2.7_randomized_cache_ttl
v3.0_fix_multiple_builds
v3.0_servers_defaults
v2.7-update_actions_triggers_v2
v2.7-update_actions_triggers
v2.6
v2.6.x-update_triggers
v2.6-4646
v2.7.1-update_actions
v2.x
v2.6.x-testing-global-multiplexing-disabled
use-wrlock-in-dns-cache-empty
v2.6.x-fix-darwin
v2.x-admin_list_ciphers
v2.x-sqlite3_pass_exts
v2.x-tap_tests_opt_ssl
v2.6.0-update_to_libhttpserver_v0.19
v2.x_router_2ports
v2.6.0-update_to_openssl_v3.1.5
v2.x-2411025
v2.x-profiling_poc1
v2.x_sha2pass_draft2
v2.x-webui_fixes
v2.6.0-more-makefile-fixes
v2.x-20230914_test
v2.x-20230913_test
v2.5.5-branch
v2.5.5-branch_255_patches
v2.x-aurora_autodiscovery-refactor_cluster_mysql_servers-gr_bootstrap_mode_2
v2.x_mysql_connector_j_fixes
v2.6-deprecate_old_clickhouse
v2.x_refactor_cluster_mysql_servers
v2.x-aurora_autodiscovery
v2.x-zd70545
v2.x-aurora_autodiscovery_shunned_promotion
v2.x-tap20230609
v2.x-test20230530
v2.x_sha2pass_draft2-TEST
v2.x-session_track_system_variables_v2
v2.x-status-variables-for-set-stmts
v2.x-enable_session_state_trackers
v2.x-increase-logging-eof_fast_forward-t
v2.x-3863-special-query
v2.x-session_track_system_variables
v2.x_refactor_read_only_action
v2.x_sha2pass_draft1
v2.2.0-sqliteserver_read_only
v2.x-digest_umap_aux-comparison
v2.4.8
v2.x-4105_4114
v2.x-3583-server_closed_conn
v2.x-group_replication_rework-SHUNNED_promotion
v2.1.0-var-global-multiplex
v2.x-CI-hostname-tap-test-fixes
v2.x-limit-version-check
v2.x-fix_deprecate_eof_warning
v2.x-3698
v2.x_tidb_replica_read
v2.x-HostGroups_attributes
v2.0.18.221009
v2.x-ci_reg_test_3273_ssl_con
TAP_test_restapi
v2.x-tap_tests_groups
v2.x-tap_test_sqlite3_server-t
PRS_3888_3903_2
PRS_3888_3903
v2.x_code_refactor_2206
v2.x-multipacket_poc_1
v2.x-impr_hg_latency_obsv
v2.x-gcc-warnings
v2.x-hg_lock_session_id
v2.x-3768
v2.x-3371
v2.x-ci_verifications
v2.x-thread_local_qps_limit
v2.x-parser_table
v2.1.1-3207
v2.x-qps_limits
v2.x-3711
v2.x-3642
v2.x-3674
v2.x-ssl3_warnings
V2
v2.3.2
v2.3.2_3646_3647
v2.x-client_err_limit_conn_timeout
v2.x-keep_multiplexing_regression_fix
v2.3.2-3628
v2.2.2-to-v2.3.0-7
v2.2.2-to-v2.3.0-7_merge
v2.2.2-to-v2.3.0-6
v2.2.2-to-v2.3.0-6_merge
v2.2.2-to-v2.3.0-5
v2.2.2-to-v2.3.0-4
v2.2.2-to-v2.3.0-3
v2.2.2-to-v2.3.0-2
v2.2.2-to-v2.3.0-1
v2.3.1
v2.0.14-70226
v2.3.0
v2.x-client_err_limit-gr_replication_lag_action
v2.2.2
v2.2.1-3603
v2.2.1-centos7-ASAN
v2.2.1
v2.2.1-3601
v2.2.1-3599
v2.2.1-3597
v2.2.1-3595
v2.2.0-restapi_server_exc_log
v2.x-3574
v2.x-3558
v2.2.0-3546-centos-7-gcc-8
v2.x-3549
v2.x-cluster_large_mysql_users
v2.x-cov_ci_verification
v2.0.14-tb1
v2.0.14-tb1-3494
v2.0.14-tb1-3488
v2.0.14-tb1-3117
v2.0.14-tb1-2762
v2.0.14-2762
v1.4.13-arm
v2.1.1-3296
v2.2.0
v2.0.18
v2.1.1
v2.0.18-3342
v2.0.18-3182
v2.1.1-3184
v2.1.0-revert-da7fdfe14
v2.0.18-revert-da7fdfe14
v1.4.13-70160
v2.0.18-3354
v2.0.18-3350
v2.0.14-3339
1.4.13-70160
v2.0.18-3339
v2.1.1-3317
v2.1.1-3319
v2.0.18-3317
v2.1.2-LBalgo
v2.0.18-1574
v2.1.2-hgman
v2.0.17
v2.1.0
v2.0.17-3288
v2.0.17-3276
v2.0.17-3273
v2.0.16
v2.0.16-3267
v2.0.16-3265
v2.0.16-3262
v2.0.16-3261
v2.1.1-3252
v2.1.1-collation
v2.0.16-3252
v2.0.16-collation
v2.1.0-parser
v2.0.16-3219
v2.0.16-3216
v2.0.16-3201
v2.0.16-2330
revert-3191-v2.0.16-3190
v2.0.16-3204
v2.0.16-3177
v2.0.16-2619
v2.0.16-3190
v2.0.16-3187
v2.1.0-70118
v2.0.16-3133
v2.0.16-3133_ci_verification
v2.0.16-3150
v2.0.16-change_user
v2.0.15
v2.0.15_amd64_fix
v2.0.15_arm64_packages
v1.4.14-ssl
v2.0.15_arm64
v2.1.0-2820
v2.0.15-sslbug
v2.0.15-KillTrx
v2.0.14
v2.0.14-ch_build_fix
v2.0.14-focal
v2.0.14-valgrind20200904
v2.1.0-3042
v2.0.14-3035
v2.0.14-3036
v2.0.14-2955
v2.0.14-vars
v2.0.14-3005
v2.0.14-3003
v2.0.14_2970_2979
v2.0.14-NOTSOCK
v2.1.0'
v2.0.14-2958
v1.4.10-zd
v2.0.13
v2.0.13-autocommit_fix
v2.1.0-2892
v2.0.13-2711
v2.0.13-duplicated_variables
v2.0.13-duplicated_variables_for_2.1.0
v2.0.12-deprecate_eof
v2.1.0-1377
v2.1.0-admin_queries
v2.0.12-var-global-multiplex
v2.1.0-var-foreign-key
v2.0.12
v2.0.12-tab-small-log
v2.0.12-var-foreign-key
v2.0.12-var-long-query-time
v2.0.12-galera-shunned
v2.1.0-admin_queries_2
v2.1.0-tap-rm-config
v2.0.12-tap-rm-config-test
v2.1.0-QP_stmt_3
v2.0.11-fix-multi-2-ci
v2.0.11-fix-multi
v2.0.11-266_0-3
2.1.0
v2.0.11
v2.1.0-track-vars
v2.1.0-track-variables
v2.0.11-track-variables
v2.0.11-2526
v2.0.11-tap-tests
v2.0.13-2698-commit1
v2.0.10-galera-pxc-maint-mode
v2.0.11-track-vars
v2.0.10-2647
v2.0.11-track
v2.0.11-track-session-vars
v2.0.9-var-array-review
v2.0.11-stats
v2.0.10
v2.0.10-centos67
v1.4.14.2
1.4.14.2
v1.4.14-show-warnings
v2.0.9
v2.0.9-var-array_2
v2.0.9-var-array
v1.4.16
v2.0.8
val214-changing_charset
v2.0.6
v1.4.16-1922_2
v1.4.13.2
v2.0.4-charset248
v2.0.5
v1.4.10-67841
v2.0.4
v2.0.4-sqlite327
v2.0.3
v2.0.2
v1.4.15
v2.0.1
v1.4.14
v2.0.0
v1.4.14-ping_shun
v1.4.14-1828
v1.4.14-latency_awareness
v1.4.12
v1.4.13-admin_deadlock
v2.0.0-improve_speed
v1.4.13
v1.4.13-autocommit_revert
v1.4.11.2
v1.4.13-ps
v2.0.0_bionic_deb_fix
v2_962
v1.4.12-1640
v1.4.11-names_tz
v1.4.12-1693
master
v1.4.11
v1.4.10
v1.4.6
v1.3.10
jenkins_test
v2.0.0-cachegrind
v1.4.9
v2.0-lab
v149_1511
v149_1382
v1.4.7-f2
v1.4.7-f1
v149_1491
v1.4.5-kub
v1.4.8
v2.0-web2
v1.4.3
v1.4.7
bsd_install_update
v1.4.2
v1.4.1-ch2
v1.4.1
v1.3.9
v1.4.1-ch
v1.3.8
v1.3.8-dev
v1.3.7
v1.3.7-dev
v1.3.6-dev
v1.4.0-clickhouse
v1.4.0
v1.3.6
v1.3.5
v1.3.5-dev
v1.4.0-955
v1.3.4
v1.3.4-dev
v1.3.3
v1.3.3-dev
v1.3.2
v1.3.2-dev
v1.3.2-766
v1.3.0h
v1.3.1-utf8mb4
1.4.0-840
v1.3.1
v1.2.6
v1.3.0
v1.4.0-797
v1.2.5-715
v1.2.5
v1.2.4-lowmem
v1.3.1-dev-mem
v1.2.0
connleak
lab-1.2.0
v1.1.2
T107_add_proxysql_consul_requirements
T89_write_consul_integration_doc
T98_consul_multi_table_config
mongoose
evhttp
SQLiteServer
1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
2.7.3
2.6.6.1
3.0.1
2.7.2
3.0.0
2.6.6
2.7.1
2.7.0
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.5.5
2.6.0
2.5.4
2.5.3
2.5.2
2.5.1
2.4.8
2.5.0
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.2
2.3.1
2.3.0
2.2.2
2.2.1
2.2.0
2.0.18
2.1.1
2.0.17
2.0.16
2.0.15
2.0.14
2.0.13
2.0.12
2.0.11
2.1.0
2.0.10
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
1.4.16
1.4.15
2.0.1
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.3.10
1.4.9
2.0.0
1.4.8
1.4.7
1.4.6
1.4.4
1.4.3
1.4.2
1.3.9
1.3.8
1.4.1
1.3.7
1.4.0
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0h
1.3.0g
1.3.0f
1.3.0e
1.3.0d
1.3.0c
v1.3.0b
1.4.5
v1.1.0
v1.1.0-rc
v1.1.1-beta
v1.1.1-beta.1
v1.1.1-beta.2
v1.1.1-beta.3
v1.1.1-beta.4
v1.1.1-beta.5
v1.1.1-beta.6
v1.1.2
v1.2.0a
v1.2.0b
v1.2.0c
v1.2.0d
v1.2.0e
v1.2.0f
v1.2.0g
v1.2.0h
v1.2.0i
v1.2.0j
v1.2.0k
v1.2.1
v1.2.2
v1.2.3
v1.2.4
v1.2.4.0923
v1.2.5
v1.2.6
v1.3.0
v1.3.0a
v1.3.0g
v1.3.0h
v1.3.1
v1.3.10
v1.3.2
v1.3.2-1
v1.3.3
v1.3.4
v1.3.5
v1.3.6
v1.3.7
v1.3.8
v1.3.9
v1.3.9-prev.1
v1.4.0
v1.4.1
v1.4.10
v1.4.11
v1.4.12
v1.4.13
v1.4.14
v1.4.15
v1.4.16
v1.4.2
v1.4.3
v1.4.4
v1.4.5
v1.4.6
v1.4.7
v1.4.8
v1.4.9
v2.0.0-beta.1
v2.0.0-rc1
v2.0.0-rc2
v2.0.1
v2.0.10
v2.0.11
v2.0.12
v2.0.13
v2.0.14
v2.0.15
v2.0.16
v2.0.17
v2.0.18
v2.0.2
v2.0.3
v2.0.4
v2.0.5
v2.0.6
v2.0.7
v2.0.8
v2.0.9
v2.1.0
v2.1.1
v2.2.0
v2.2.1
v2.2.2
v2.3.0
v2.3.1
v2.3.2
v2.4.0
v2.4.1
v2.4.2
v2.4.3
v2.4.4
v2.4.5
v2.4.6
v2.4.7
v2.4.8
v2.5.0
v2.5.1
v2.5.2
v2.5.3
v2.5.4
v2.5.5
v2.6.0
v2.6.1
v2.6.2
v2.6.3
v2.6.4
v2.6.5
v2.6.6
v2.7.0
v2.7.1
v2.7.2
v2.7.3
v3.0.0-alpha
v3.0.1
v3.0.2
v3.0.3
v3.0.4
v3.0.5
v3.0.6
v3.0.7
v3.0.8
v3.0.9
v3.1.6
v3.1.7
v3.1.8
v3.1.9
v4.0.6
v4.0.7
v4.0.8
${ noResults }
75 Commits (6cdd4e697eb86982e3dfd2ceab009ead5da0efcb)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
960eeab8d2 |
feat(mysqlx): wire per-route tls_mode through handler_capabilities_set entry path
PR #5709 landed the splice mechanics for tls_mode='passthrough' plus the per-route tls_mode column on mysqlx_routes, but no code consulted the route's tls_mode to actually drive the session into X_PASSTHROUGH_FORWARD from real client traffic — a route configured as `tls_mode='passthrough'` behaved identically to `'inherit'`. This commit closes the gap (issue #5710): (1) Advertise gating in send_capabilities() ----------------------------------------- A new helper, MysqlxSession::effective_route_tls_mode(), reads listener_route_name_ + the thread's MysqlxConfigStore and returns the per-route tls_mode (or `inherit` when listener route is empty / config store is unwired — preserving historical behaviour for tests and unconfigured deployments). send_capabilities() now suppresses the `tls` capability from the advertised set when route_tls_mode == disabled. Other modes (inherit / preferred / required / passthrough) advertise TLS the same way as before — passthrough MUST advertise so the client even thinks to upgrade. (2) Symmetric refusal in handler_capabilities_set() ------------------------------------------------ A client that sends CapabilitiesSet(tls=true) on a disabled-mode route is refused with X-Protocol error 5052 ("TLS is not enabled on this route"). Symmetric with the advertise gate; we never told the client TLS was available, so accepting it would be a silent policy bypass. (3) Passthrough entry from handler_capabilities_set() -------------------------------------------------- When CapabilitiesSet(tls=true) lands on a passthrough-mode route: a. resolve_passthrough_backend_target() picks the endpoint from the route's destination_hostgroup (no identity_ — passthrough authenticates end-to-end); b. the original CapabilitiesSet frame bytes are buffered in passthrough_pending_capset_frame_ for verbatim forwarding; c. tls_mode_ flips to TLS_PASSTHROUGH and status_ to the new X_PASSTHROUGH_BACKEND_CONNECTING state. (4) X_PASSTHROUGH_BACKEND_CONNECTING handler ----------------------------------------- handler_passthrough_backend_connecting() drives the four-step sequence: 1. allocate / pick up backend_conn_, start non-blocking TCP connect; 2. poll check_connect() to completion; 3. write the buffered CapabilitiesSet bytes verbatim to the backend (with EAGAIN/partial-write retries); 4. read exactly one X-Protocol frame from the backend (the LAST X-Protocol parse this session does); on Ok forward to the client and transition to X_PASSTHROUGH_FORWARD; on Error propagate to the client and close. The handler is registered both in the early fast-path branch of MysqlxSession::handler() (so the X-Protocol read on client_ds_ is skipped — the client's next bytes are the TLS ClientHello which is not parseable as an X frame) and in the main dispatch switch. Tests (test/tap/tests/unit/mysqlx_session_unit-t.cpp, +11 ok()s): * test_route_tls_mode_disabled_no_tls_in_advertise: drives CapabilitiesGet on a tls_mode='disabled' route, parses the response, asserts the advertised set has no `tls` capability. * test_capabilities_set_tls_refused_on_disabled_route: drives CapabilitiesSet(tls=true) on the same route, asserts session becomes unhealthy and the client receives a Mysqlx::Error. * test_route_passthrough_full_entry_path: end-to-end exercise. Stands up a loopback TCP listener as the "backend"; configures a passthrough route pointing at the listener; drives CapabilitiesGet -> CapabilitiesSet(tls=true); asserts the proxy forwards the CapabilitiesSet bytes verbatim, transitions through X_PASSTHROUGH_BACKEND_CONNECTING into X_PASSTHROUGH_FORWARD on the backend's Ok response, and stays healthy. Plan count: 87 -> 98 (after Fix B) -> 109. Closes #5710. |
2 months ago |
|
|
b7c5a08b96 |
feat(mysqlx): backlog and resume EAGAIN'd writes in passthrough splice
Previously, the passthrough splice loop's pump_one_direction helper
treated EAGAIN/EWOULDBLOCK on write() as fatal — the session
transitioned to X_SESSION_CLOSING and the connection was dropped. In
practice this turned any transient destination back-pressure (slow
client, slow backend, kernel send-buffer drained more slowly than the
proxy reads from the source side) into a connection drop. End-to-end
encrypted sessions (the whole point of tls_mode='passthrough') would
RST mid-stream under bursty load.
Fix: per-direction write backlog, drained before the next read.
* Add MysqlxSession::passthrough_c2b_backlog_ and
passthrough_b2c_backlog_ — std::vector<uint8_t> buffers for
bytes that read() from the source side could not be immediately
flushed to the destination side. Cleared on init() / reset().
* handler_passthrough_forward() now runs a drain pass over the
backlog BEFORE the read pass. drain_backlog() loops on write(),
EAGAIN means "not now, leave it for the next tick"; any other
write error fails the session.
* On a partial-write inside the read pass, the unwritten tail is
appended to the backlog (append_to_backlog) and the read pass
yields. A future pump tick (driven by either the libev EV_READ
wakeup on the source or the buffered-work re-arm via
`to_process = true`) drains the backlog and resumes reading.
* Backlog cap: 1 MiB per direction
(PASSTHROUGH_BACKLOG_CAP). Above the cap, append_to_backlog
refuses and the session is killed (X_SESSION_CLOSING) — slow-
consumer DoS protection. The drain-first contract limits the
backlog organically to BURST_BYTES (64 KiB), so the cap is
purely defensive against a future logic bug or a larger burst.
* to_process = true on a non-empty backlog so the outer dispatch
loop re-enters us on the next tick rather than waiting for a
new fd-readable event on the source — without this, a long
stall on the destination side could let the backlog grow on
each new source byte before the previous backlog was drained.
Tests (test/tap/tests/unit/mysqlx_session_unit-t.cpp):
* test_passthrough_eagain_backlog_drains_across_ticks: drives
256 KiB through a backend leg with SO_SNDBUF/RCVBUF shrunk to
4 KiB so write() returns EAGAIN early and often. Asserts
session stays healthy in X_PASSTHROUGH_FORWARD across the
repeated EAGAIN events; all 256 KiB are eventually delivered;
no byte-level corruption.
* test_passthrough_backlog_cap_kills_session: uses the
MYSQLX_TEST_BUILD-only seed_passthrough_c2b_backlog_for_test
+ try_append_to_passthrough_c2b_backlog_for_test helpers to
drive the cap branch directly. Asserts within-cap append is
accepted, over-cap append is refused, and the session
transitions to X_SESSION_CLOSING with healthy=false.
Plan count moves from 87 to 98.
|
2 months ago |
|
|
0ccf3615f1 |
fix(mysqlx): classify backend TLS handshake failures into specific error codes
Closes #5698 (P3 — meaningful TLS error messages on backend failures). Background: prior to this commit, backend TLS handshake failures all emitted a single generic "Backend TLS handshake failed" with code 3152; frontend TLS failures all emitted "TLS handshake failed" code 3151. Operators debugging TLS issues had to attach with `openssl s_client` or run the proxy under SSL_TRACE / SSLKEYLOGFILE to figure out whether the underlying cause was an expired cert, a hostname mismatch, an untrusted CA, or a protocol-version mismatch. MySQL Router translates each of these into a distinct X-Protocol error code and shortcuts the debug session. Implementation: 1. New MysqlxTlsErrorClass enum (HANDSHAKE_FAILED, CERT_VERIFY_FAILED, CERT_EXPIRED, HOSTNAME_MISMATCH, PROTOCOL_MISMATCH, UNKNOWN_CA, NO_SSL_CTX, UNKNOWN). Lives in mysqlx_protocol.h alongside the per-class X-Protocol error code constants. 2. New mysqlx_classify_tls_error(SSL*, peek_err_queue) helper. Classification logic in order: a. SSL == nullptr -> NO_SSL_CTX b. SSL_get_verify_result() != X509_V_OK -> chain class (CERT_EXPIRED for expired/not-yet-valid; HOSTNAME_MISMATCH; UNKNOWN_CA for self-signed-in-chain or unable-to-get-issuer; CERT_VERIFY_FAILED for everything else cert-chain) c. ERR_get_error() walked for SSL_R_UNSUPPORTED_PROTOCOL / TLSV1_ALERT_PROTOCOL_VERSION / WRONG_VERSION_NUMBER / UNKNOWN_PROTOCOL -> PROTOCOL_MISMATCH d. Fallback -> HANDSHAKE_FAILED Cert-chain reasons take precedence because the OpenSSL queue sometimes carries both, and the chain reason is more actionable. 3. Backend codes (5+ distinct, 3152-3157): 3152 BACKEND_TLS_ERR_HANDSHAKE_FAILED (was: only existing code) 3153 BACKEND_TLS_ERR_CERT_VERIFY_FAILED (NEW) 3154 BACKEND_TLS_ERR_CERT_EXPIRED (NEW) 3155 BACKEND_TLS_ERR_HOSTNAME_MISMATCH (NEW) 3156 BACKEND_TLS_ERR_PROTOCOL_MISMATCH (NEW) 3157 BACKEND_TLS_ERR_UNKNOWN_CA (NEW) Backend messages name the OpenSSL reason in operationally-useful terms ("certificate hostname mismatch") but do NOT include the raw OpenSSL queue string in the wire frame — that is logged to stderr separately for the operator's benefit. 4. Frontend codes (3 distinct): 3150 FRONTEND_TLS_ERR_NOT_CONFIGURED (existing — NO_SSL_CTX) 3151 FRONTEND_TLS_ERR_HANDSHAKE_FAILED (existing — every other class) 3158 FRONTEND_TLS_ERR_PROTOCOL_MISMATCH (NEW — operationally useful for legitimate clients hitting a too-old/too-new TLS version) Frontend collapses CERT_*/HOSTNAME_MISMATCH/UNKNOWN_CA onto HANDSHAKE_FAILED specifically to avoid leaking attacker-supplied cert info into the response (asymmetric threat model — the frontend client may be the attacker). 5. New MysqlxConnection::tls_error_class_ member + accessors. Set by step_auth_tls_handshake() at the failure site (the OpenSSL error queue is thread-local FIFO and must be drained while fresh; we can't defer the classification to the session). Read by the session's BACKEND_AUTH_ERROR branch in handler_connecting_server when emitting send_error(). 6. Frontend handler_tls_accept_init() also runs the classifier and emits via the frontend code/message helpers. Logs the OpenSSL queue to stderr (operator-side only). Tests (in mysqlx_tls_unit-t): - test_classify_null_ssl: nullptr -> NO_SSL_CTX (1 ok) - test_classify_cert_expired_via_verify_result (1 ok) - test_classify_hostname_mismatch_via_verify_result (1 ok) - test_classify_unknown_ca_via_verify_result: 2 sub-cases (UNABLE_TO_GET_ISSUER + SELF_SIGNED_IN_CHAIN) (2 ok) - test_classify_generic_cert_verify_failed: SIGNATURE_FAILURE -> generic (1 ok) - test_classify_handshake_failed_default (1 ok) - test_classify_code_message_round_trip_backend: 7 classes * 2 (msg + code-range) + 1 distinct-codes assertion (15 ok) - test_classify_code_message_round_trip_frontend: collapse + 3 distinct-code asserts (4 ok) The cert-fixture-driven tests (real expired/hostname-mismatched/ self-signed cert chains) are explicitly punted per the issue's "don't generate cert fixtures in this PR" guidance. The classifier accepts SSL_set_verify_result-staged synthetic state, which exercises the same code paths as a real cert failure (since the only thing the classifier reads off SSL is the verify_result long); a follow-up issue covers wiring an end-to-end TAP test against a fixture-driven backend. Build: NOJEMALLOC=1 WITHASAN=1 PROXYSQLGENAI=1 plugin .so + build_tap_test_debug all green. Pre-existing RSA_new deprecation warnings unchanged. Tests: mysqlx_tls_unit-t (44/44, was 18 — +26 from 8 new tests). Other suites unchanged. ASAN-clean. |
2 months ago |
|
|
767f808309 |
fix(mysqlx): mark post-Session::Reset connections non-cacheable
Closes #5697 (P3 — Session Reset pool invalidation). Background: X-Protocol's Session::Reset frame asks the server to wipe session state (current schema, isolation level, charset, prepared statements, session vars) without reauthenticating. ProxySQL forwards the frame and dispatches the backend Ok response correctly — but until this commit, the connection was returned to the per-thread cache as a regular IDLE entry afterwards. The cache key (hostgroup, user, schema, tls_active) doesn't capture session-state-version, so a subsequent reuse from the same identity could pick up the post-reset connection and silently inherit blank state instead of the per-identity defaults. The pool-leak scenario in practice: - Client A issues Session::Reset on backend B. - Backend B is returned to the per-thread cache as IDLE. - Client A2 (same user/schema/hostgroup/tls posture) next request pulls B from the cache. - Client A2 sees blank session state, even though it expected the per-identity defaults a fresh-auth connection would provide. MySQL Router treats Session::Reset as pool-terminating; this commit brings ProxySQL in line. Implementation: - New MysqlxConnection::needs_post_reset_rehandshake_ bool member + accessor pair (set/get). Default false. - is_reusable() returns false when the flag is set, so Mysqlx_Thread::return_connection_to_cache deletes the connection instead of pooling it. - reset() defensively clears the flag too. Production path doesn't rely on this (the cache deletes non-reusable conns instead of resetting them), but the invariant matters for any future code path that calls reset() directly (e.g. retry-on-error). - handler_session_reset_waiting() sets the flag on the Mysqlx.Ok branch — the only path where Session::Reset succeeded. The Error branch already marks the connection non-reusable for unrelated reasons (the Reset itself failed), so no change needed there. We do NOT implement the rehandshake path itself in this commit; the simpler "drop on reset" semantic matches Router's behaviour and is strictly safer than the pre-fix "silently pool blank state". Tests: - mysqlx_connection_unit-t::test_connection_post_reset_rehandshake_flag asserts the flag's defaults, set/get round-trip, is_reusable() interaction, and reset() defensive-clear behaviour (6 ok()). - mysqlx_message_dispatch_unit-t::test_dispatch_sess_reset_marks_non_cacheable drives the full SESS_RESET → backend-Ok → return-to-pool flow with a fake backend and asserts the connection is non-cacheable afterwards. Strengthens the existing test_dispatch_sess_reset which only proved the dispatch reached the backend (5 ok() — pre-flag/state, post-dispatch status, post-condition observed via backend_conn being deleted on the non-reusable path). - mysqlx_connection_unit-t plan switched to plan(0) so future additions don't require re-counting. Build: NOJEMALLOC=1 WITHASAN=1 PROXYSQLGENAI=1 plugin .so + build_tap_test_debug all green, no compile warnings. Tests: mysqlx_connection_unit-t (16/16, was 10), mysqlx_message_dispatch_unit-t (114/114, was 109). Other suites unchanged. ASAN-clean. |
2 months ago |
|
|
31a3934014 |
fix(mysqlx): explicit auth-phase notice forwarding policy in step_auth
Closes #5695 part 2 (P2 — explicit per-state policy for backend NOTICE frames during the backend auth handshake; data-plane validation landed in the previous commit). Background: prior to this commit, MysqlxConnection::read_auth_frame() silently drained ALL backend NOTICE frames during the BACKEND_AUTH_* state machine. The drain was needed (MySQL backends commonly emit a SESSION_STATE_CHANGED notice before AuthenticateContinue / Ok, and returning nullopt on a NOTICE caused auth to spin until the 10s handshake timeout) but it was indiscriminate — a malformed/unknown-type NOTICE would be drained as silently as a legitimate one. This commit adds an explicit per-state policy: - New helper auth_phase_notice_is_drainable(body, body_len). Parses the NOTICE as a Mysqlx::Notice::Frame and applies a per-type decision: WARNING / SESSION_VARIABLE_CHANGED / SESSION_STATE_CHANGED / GROUP_REPLICATION_STATE_CHANGED / SERVER_HELLO -> drain malformed / empty / unknown enum value -> fail auth WARNING and GROUP_REPLICATION_STATE_CHANGED are also logged on the drain path: WARNING during auth typically signals a server-side misconfig (deprecated auth method, etc.); GROUP_REPLICATION_* during auth is operationally out-of-place (cluster-membership notices belong on data-plane connections, not handshakes). Both are drained to preserve compatibility with over-eager backends but surfaced for operator triage. - Wired into read_auth_frame() between the MAX_LEADING_NOTICES cap and the type==NOTICE branch. On a fail-auth result, the helper has already set auth_state_=BACKEND_AUTH_ERROR and logged; the caller propagates std::nullopt and step_auth fails the connection. - Frontend forwarding during auth: explicitly NEVER allowed. The frontend client has no context to interpret a backend NOTICE before it sees AuthenticateOk; surfacing them would also leak server-side state mid-handshake (the load-bearing concern in issue #5695). The proxy terminates ALL backend NOTICEs on the backend leg during the backend auth phase — even legitimate ones. - Test-only hooks under MYSQLX_TEST_BUILD: a public auth_phase_notice_is_drainable_for_test() and a set_auth_state_for_test() so unit tests can drive the per-type decision matrix directly without running the full step_auth state machine. Tests (in mysqlx_backend_auth_unit-t): - test_auth_phase_notice_known_types_drained: all five spec types are drainable and don't perturb auth_state_ (5*2=10 ok()). - test_auth_phase_notice_unknown_type_fails_auth: type=99 fails auth and sets BACKEND_AUTH_ERROR (2 ok()). - test_auth_phase_notice_malformed_fails_auth: garbage protobuf bytes fail auth (2 ok()). - test_auth_phase_notice_empty_fails_auth: empty body fails auth (2 ok()). - Switched the existing plan(42) to plan(0) so future additions don't require re-counting; existing test_backend_auth_notice_skip (which uses Frame_Type_WARNING) continues to pass under the new policy because WARNING is still on the drainable list. Build: NOJEMALLOC=1 WITHASAN=1 PROXYSQLGENAI=1 plugin .so + build_tap_test_debug all green, no compile warnings. Tests: mysqlx_backend_auth_unit-t (58/58, was 42 — +16 from the 4 new tests), other suites unchanged. ASAN-clean. |
2 months ago |
|
|
6ef8cf3f1f |
fix(mysqlx): validate Mysqlx::Notice::Frame::type before forwarding
Closes #5695 part 1 (P2 — explicit notice forwarding awareness; auth-phase per-state policy is the next commit; per-notice metric remains under #5691 follow-up scope). Background: X-Protocol NOTICE frames are non-terminal informational messages a backend can emit at essentially any state transition (warnings, session-state changes, generated-doc-id notifications, group-replication membership changes, etc.). Prior to this commit the proxy forwarded NOTICE frames uncritically; a buggy or hostile backend (or an MITM that bypassed TLS) could ship a NOTICE frame with an unknown `type` enum value and confuse a strict client. The risk is bounded — info disclosure, no auth bypass — but it's unhardened territory MySQL Router handles explicitly. This commit adds a per-frame validation hook on the data plane: - New MysqlxSession::is_notice_frame_valid(body, body_len). Pure query — no session-state mutation. Parses the body as a Mysqlx::Notice::Frame and verifies the outer `type` field is in the spec range (1..5: WARNING, SESSION_VARIABLE_CHANGED, SESSION_STATE_CHANGED, GROUP_REPLICATION_STATE_CHANGED, SERVER_HELLO) using the protoc-generated Frame_Type_IsValid(). Empty body, malformed protobuf, missing required `type` field, and out-of-range enum values all return false. - Wired into handler_waiting_server_msg() and handler_session_reset_waiting() before forwarding NOTICE to the client. Invalid notices are dropped with a stderr log line tagged with route+hostgroup+body_len for operator triage; the response sequence continues (NOTICE is non-terminal in every state). - Test-only hook is_notice_frame_valid_for_test() exposed under MYSQLX_TEST_BUILD so unit tests can drive the predicate directly with synthetic bodies without setting up a fake backend. Tests (in mysqlx_message_dispatch_unit-t): - test_notice_validation_known_types_accepted: all five spec types pass (5 ok()). - test_notice_validation_unknown_type_rejected: types 0, 99, and 100000 are rejected (3 ok()). - test_notice_validation_empty_body_rejected: nullptr body and zero-length body both rejected (2 ok()). - test_notice_validation_malformed_protobuf_rejected: garbage bytes that fail ParseFromArray are rejected (1 ok()). We deliberately do NOT validate the inner `payload` bytes — that's a type-specific protobuf the client parses, and the proxy has no business reaching into it. The outer `type` is the load-bearing field for client-side branching, so that's where we focus. Build: NOJEMALLOC=1 WITHASAN=1 PROXYSQLGENAI=1 make build_lib + plugin .so + build_tap_test_debug all green. Tests: mysqlx_session_unit-t (87/87), mysqlx_message_dispatch_unit-t (109/109, was 98 — +11 from the 4 new tests), mysqlx_compression_unit-t (64/64), mysqlx_tls_unit-t (18/18), mysqlx_thread_unit-t (25/25), mysqlx_concurrent_unit-t (6/6). All ASAN-clean. |
2 months ago |
|
|
2e7e532050 |
feat(mysqlx): X_PASSTHROUGH_FORWARD session state — raw byte splice
Implements the data-plane half of the per-route TLS passthrough mode (issue #5692). Once a session enters this state the proxy stops parsing X-Protocol frames; bytes are read from one side and written to the other verbatim using read(2)/write(2). Used cases: end-to-end TLS where operator policy forbids proxy MITM (compliance, original cert/SNI/ALPN preservation). Session machine additions: * `MysqlxSession::Status::X_PASSTHROUGH_FORWARD` — terminal state for passthrough sessions. Once entered, the session never returns to any X-Protocol parsing state. handler() short-circuits past client_ds_.read_from_net()/parse so the bytes are not interpreted as frames. * `MysqlxTlsMode::TLS_PASSTHROUGH` — added back to the per-session TLS posture enum. The previous prototype carried the value without a real implementation; that was removed in the asymmetric-TLS series and is now reintroduced as a real feature. * `handler_passthrough_forward()` — pumps both directions per call up to a 64 KiB burst, then yields so other sessions on the thread are not starved. EAGAIN/EWOULDBLOCK is the normal "no more bytes right now" exit. EOF on either side or any other I/O error transitions to X_SESSION_CLOSING. Listener-route propagation: * New `MysqlxSession::init(fd, thread, listener_route)` overload. The base init(fd, thread) overload calls it with an empty route name, preserving existing test harnesses that construct sessions directly. * `Mysqlx_Thread::accept_new_connection()` looks up the listener fd in the parallel `listener_route_names_` vector and propagates the route name to the new session at accept time. This is the hook that lets per-route policies fire before any X-Protocol message is received — most importantly, before any local handshake decision so a passthrough route can splice from the very first byte. Test-only entry path: * `enter_passthrough_for_test(int backend_fd)` (gated behind MYSQLX_TEST_BUILD) drops a session straight into the splice state with a caller-supplied backend fd. Bypasses CapabilitiesSet / auth / resolve_backend_target so the splice mechanics can be asserted in isolation. set_reusable(false) is set on the stub backend connection to mirror the production-side invariant that a passthrough connection never returns to the pool. Tests (mysqlx_session_unit-t, +22 assertions, plan 65 -> 87): * listener_route_propagation: init() captures, reset() clears * forward_client_to_backend / forward_backend_to_client: bytes survive the splice unchanged in both directions * close_on_client_eof / close_on_backend_eof: peer half-close transitions to X_SESSION_CLOSING and marks the session unhealthy * disables_backend_reuse: backend_conn() exists but is_reusable()=false, and tls_mode_ reports TLS_PASSTHROUGH * handler_dispatch_skips_xprotocol: bytes that would parse as a fake X frame in a non-passthrough state survive the splice unconsumed — proves handler() takes the fast path before the X frame parser sees the buffer Production wiring of the entry path (CapabilitiesSet on a passthrough route -> connect to backend, splice the TLS handshake) lands in a follow-up. This commit is intentionally limited to the splice mechanics plus the listener-route plumbing the entry path will need. |
2 months ago |
|
|
09c42d4fba |
feat(mysqlx): add per-route tls_mode column with passthrough enum value
Adds the schema and config-store plumbing needed to express MySQL Router's
TLS-Passthrough mode as a per-route attribute on `mysqlx_routes`. The actual
data-plane forwarding is implemented in a follow-up commit; this commit only
wires the config so the operator can set / round-trip / project the value.
Schema additions:
* `mysqlx_routes.tls_mode VARCHAR NOT NULL DEFAULT 'inherit'` plus a CHECK
constraint on the canonical lowercase spellings ('inherit', 'disabled',
'preferred', 'required', 'passthrough'). Same column added to
`runtime_mysqlx_routes`.
* Default 'inherit' means "use the deployment-wide `mysqlx_tls_mode`",
preserving existing behaviour for every row that was loaded before this
commit. Operators wanting compliance-pinned passthrough can override one
route at a time without flipping the global mode.
Config store additions:
* `MysqlxRouteTlsMode` enum class (inherit / disabled / preferred / required
/ passthrough) plus matching `mysqlx_route_tls_mode_from_string` (case-
insensitive, empty/NULL -> inherit, unknown -> nullopt) and `_to_string`
helpers — same shape as the existing `mysqlx_backend_tls_mode_*` family.
* `MysqlxRoute::tls_mode` field with a default of `inherit`.
* `MysqlxConfigStore::route_tls_mode(name)` accessor; unknown routes report
`inherit` (matching how `route_hostgroup` returns 0 for unknown routes).
LOAD / SAVE / project paths:
* `install_routes_from_admin` first PRAGMA-probes for the `tls_mode` column
so a pre-upgrade admin DB without it loads cleanly (column treated as
NULL -> inherit). A malformed value (e.g. operator typo) fails the install
with a descriptive `err` rather than silently coercing.
* `save_routes_to_admin_table` and `project_routes_to_runtime_view` write
the column using `mysqlx_route_tls_mode_to_string`.
Tests:
* mysqlx_config_store_unit-t gains nine assertions covering parser
case-insensitivity, the empty-string / unknown-value contract, the
canonical render, the legacy-schema fallback path, and a full
install / parse-failure round-trip against a schema that has the column.
* All previously-passing unit tests remain green.
This commit is intentionally behaviour-preserving: passthrough is parsed
and stored but not yet enforced. Resolving a route with tls_mode=passthrough
still goes through the proxy-terminated path until the follow-up commit
adds the X_PASSTHROUGH_FORWARD session state.
|
2 months ago |
|
|
167a896c16 |
feat(mysqlx): mode-driven backend TLS decision + tls_active conn-cache key
Implements the per-session decision side of issue #5693 (P1: asymmetric TLS / AsClient mode parity gap with MySQL Router 8.0). The previous commit added the runtime variable; this commit wires it through to the actual backend-connect path and partitions the connection pool by encryption posture so AsClient/required-TLS sessions never reuse plaintext-pooled backends (and vice versa). What changes: * Lifts the per-session decision out of an inline switch in MysqlxSession::handler_connecting_server() into a pure helper `mysqlx_resolve_backend_tls_decision(mode, endpoint_override, frontend_is_encrypted) -> {require_tls, fallback_allowed}`. The helper lives at file scope (not in the anonymous namespace) so the unit test can exercise the 8 (mode x frontend_tls) combinations called out in the issue acceptance criteria directly, without driving the full session state machine. The decision itself replaces the legacy `target_use_ssl_ || client_ds_.is_encrypted()` expression. * Adds two fields on MysqlxConnection: * tls_active_ -- set true by step_auth_tls_handshake() once the OpenSSL handshake completes successfully on the backend leg. Read by the connection cache to partition encrypted-pooled connections from plaintext-pooled ones. * backend_tls_fallback_allowed_ -- carries the `preferred` mode's "downgrade-to-plaintext-on-error" intent through to the auth state machine. Read-only metadata in this commit; the actual fallback path lands in the next commit. * Mysqlx_Thread::get_connection_from_cache now takes a `bool tls_active` parameter and matches on it. Without this, an AsClient TLS session pulling a pooled connection could land on a plaintext backend (or vice versa), corrupting the wire protocol when the next dispatched frame goes out over a socket in the wrong encryption posture. * Endpoint-override semantics (mysqlx_backend_endpoints.use_ssl=1) preserved exactly: it can promote plaintext to TLS regardless of mode, but cannot demote a TLS-required mode to plaintext. Under mode=preferred, the override leaves fallback_allowed=true so the operator's best-effort "preferred" intent isn't silently upgraded to "required". Tests: test/tap/tests/unit/mysqlx_message_dispatch_unit-t (was 86): adds 8 combinations (mode x frontend_tls) + 4 endpoint-override cases = 12 new ok lines, total 98. test/tap/tests/unit/mysqlx_thread_unit-t (was 22): adds test_connection_cache_tls_partition with 3 ok lines exercising the (plaintext, encrypted) hard partition, total 25. Known limitation (TODO addressed in next commit): mode=preferred currently fails the backend connect on Mysqlx::Error from CapabilitiesSet(tls=true) because the fallback-to-plaintext branch isn't wired in step_auth_capabilities_set_sent yet. The metadata (`backend_tls_fallback_allowed_` on the connection) is in place; only the state machine branch is missing. Two of the 8 documented test combinations (mode=preferred + Error from backend) are validated against the metadata only in this commit; the end-to-end fallback behaviour follows. Tested under NOJEMALLOC=1 WITHASAN=1 PROXYSQLGENAI=1. Refs #5693. Stacks on #5706 / #5704. |
2 months ago |
|
|
0246237426 |
feat(mysqlx): add MysqlxBackendTlsMode enum + mysqlx_tls_backend_mode runtime variable
Wires up the configuration plumbing for issue #5693 (P1: asymmetric TLS / AsClient mode parity gap with MySQL Router 8.0). This commit is behaviour-neutral: the new variable is parsed, validated, persisted and exposed via MysqlxConfigStore::get_backend_tls_mode(), but the per- session backend-TLS decision still uses the legacy target_use_ssl_ || client_ds_.is_encrypted() expression. The decision site is rewritten in the next commit. What this adds: * MysqlxBackendTlsMode enum with four values matching MySQL Router's client_ssl_mode / server_ssl_mode taxonomy: disabled, preferred, required, as_client. Default is as_client because that most closely matches the legacy implicit behaviour where the backend leg encryption was tied to the frontend leg's encryption. * mysqlx_backend_tls_mode_from_string() / mysqlx_backend_tls_mode_to_string() for case-insensitive parsing and canonical lower-case rendering. The parser returns std::optional so the install path can surface a useful error to the operator on a typo instead of silently coercing to a default. * MysqlxConfigStore now reads the mysqlx_tls_backend_mode key from mysqlx_variables in install_variables_from_admin(), persists it via save_variables_to_admin_table(), and projects it in project_variables_to_runtime_view(). install fails atomically with a descriptive error when the value is unrecognised; an absent row leaves the cached mode untouched (matches how the other tunables already behave). * MysqlxBackendEndpoint.use_ssl=1 remains an operator-controlled override that forces TLS regardless of the mode (per existing comment at handler_connecting_server). The mode interacts with that flag in the next commit. Tests: test/tap/tests/unit/mysqlx_config_store_unit-t: 16 -> 24 assertions. New coverage: parser accepts all four documented values case-insensitively, parser rejects unknown values, default mode is as_client, LOAD round-trip caches the parsed mode, invalid value fails install with descriptive error, store retains last-good mode after rejected install, absent row leaves cached mode untouched. Tested under NOJEMALLOC=1 WITHASAN=1 PROXYSQLGENAI=1. Stacks on PR #5706 (mysqlx response state machines), which itself stacks on PR #5704 (mysqlx observability P0). Refs #5693. |
2 months ago |
|
|
170ead7ad6 |
fix(mysqlx): reject backend frames disallowed in current response state
Tightens MysqlxSession::is_frame_allowed: a backend frame outside the
per-state allowed set now closes the session with X-Protocol Error 4006
("Backend sent an unexpected message in the current response state")
instead of being forwarded blindly to the client. This guards against
a buggy or hostile backend pushing a frame whose shape the client cannot
parse, which would silently desync the wire and (in the worst case)
amplify a backend protocol bug into a client-side fault.
Adds a new private sub-state field MysqlxSession::seen_column_metadata_
that gates RESULTSET_ROW frames in the four response states where the
X-Protocol requires ColumnMetaData to precede any Row:
- RESP_WAITING_STMT_EXECUTE
- RESP_WAITING_CRUD
- RESP_WAITING_PREPARE_EXECUTE
- RESP_WAITING_CURSOR_OPEN
The flag is set when ColumnMetaData is forwarded in the validation loop,
cleared at every transition into a state that begins a new column-
metadata sequence (in dispatch_client_message), at terminal-frame flush
in handler_waiting_server_msg, and at init() / reset(). It is
deliberately NOT cleared on transition into RESP_WAITING_CURSOR_FETCH —
per the X-Protocol spec, ColumnMetaData is sent at Cursor::Open and not
re-sent at Cursor::Fetch, so CURSOR_FETCH's allowed-set unconditionally
accepts RESULTSET_ROW. The dispatch handler skips the clear at
CURSOR_FETCH entry to make this carry-across explicit.
The rejection action in handler_waiting_server_msg pops the offending
frame from the backend queue (does not forward), emits a fatal error
frame to the client, marks backend_conn_ non-reusable so
return_backend_to_pool deletes it instead of caching a poisoned
connection, sets healthy=false and status_=X_SESSION_CLOSING, and
short-circuits before the bytes_recv accounting (so the disallowed
frame is not double-counted as forwarded traffic).
Tests in mysqlx_message_dispatch_unit-t (20 new assertions across
8 cases) cover:
1. StmtExecute → Row-without-metadata (canonical hostile-backend
case the issue called out): expect Error frame, !healthy,
X_SESSION_CLOSING.
2. StmtExecute → ColumnMetaData → Row → SQL_STMT_EXECUTE_OK: happy
path, terminal flushes seen_column_metadata_.
3. CursorOpen → ColumnMetaData → Row → FETCH_SUSPENDED: terminal,
response_state_ resets, flag cleared at boundary (Cursor::Fetch
does not consult it).
4. CursorOpen → ColumnMetaData → FETCH_DONE: terminal, clean exit.
5. PreparePrepare → SQL_STMT_EXECUTE_OK (allowed-set is OK-only):
expect rejection, X_SESSION_CLOSING.
6. PreparePrepare → OK: happy path.
7. STMT_EXECUTE → ColumnMetaData → NOTICE → Row → SQL_STMT_EXECUTE_OK:
NOTICE is universal-allowed, non-terminal, doesn't consume the
response.
8. CURSOR_FETCH → Row (with seen_column_metadata_=false): forwarded,
NOT rejected (the per-state-pair carve-out for Cursor::Fetch).
Tests use a new setup_session_for_validation helper that drives the
auth flow, attaches a fake backend over a socketpair, parks the
session in WAITING_SERVER_XMSG with the desired response_state_ via
the test-only set_response_state_for_test (gated behind
MYSQLX_TEST_BUILD), and lets the test write synthetic server frames
to the backend half of the pair. Drains pending client-side bytes via
a non-blocking helper so the validation hook's output is unambiguous.
Closes the rejection-side acceptance criterion of #5694. Refs: #5694.
|
2 months ago |
|
|
34968e08d4 |
fix(mysqlx): split CURSOR/PREPARE response states for accurate terminal detection
CURSOR_OPEN's terminal set was conflated with CURSOR_FETCH's, and
PREPARE_PREPARE/DEALLOCATE were lumped with PREPARE_EXECUTE which has a wider
terminal set. Adds RESP_WAITING_CURSOR_{OPEN,FETCH,CLOSE} and
RESP_WAITING_PREPARE_{PREPARE,EXECUTE,DEALLOCATE} so each X-Protocol response
shape gets its own per-state contract.
The two old conflated states accepted the union of all terminal frames
that any sub-shape could legitimately emit, so the proxy would advance to
RESP_IDLE on the first such frame regardless of whether the backend's
response was actually shaped right. With the split:
- PREPARE_PREPARE / PREPARE_DEALLOCATE / CURSOR_CLOSE accept Mysqlx.Ok only
- PREPARE_EXECUTE accepts Ok / SQL_STMT_EXECUTE_OK / FETCH_DONE / FETCH_SUSPENDED
(it inherits the response shape of whichever request was prepared)
- CURSOR_OPEN / CURSOR_FETCH accept FETCH_DONE / FETCH_SUSPENDED
This is purely a tightening of is_terminal_frame; the validation hook
that uses these per-state contracts to actively reject out-of-shape
backend frames is added in the next commit. No behaviour change for
well-behaved backends; existing dispatch tests (66 assertions in
mysqlx_message_dispatch_unit-t, 65 in mysqlx_session_unit-t) continue
to pass unmodified.
Refs: #5694.
|
2 months ago |
|
|
b812045e11 |
refactor(mysqlx): split is_terminal_for_state into is_frame_allowed + is_terminal_frame
No behavior change. Permissive is_frame_allowed (allows everything the existing code implicitly allows by forwarding) sets up the validation hook for the follow-on commits. Removes the now-unused is_terminal_server_frame_generic helper since every state has an explicit case. The new is_terminal_frame variant centralises the universal-NOTICE rule: NOTICE is never terminal in any state, ERROR always is. Previously the caller in handler_waiting_server_msg() short-circuited NOTICE at the call site; folding that into the predicate keeps the validation hook (added in the next commits) from having to know about NOTICE separately. Refs: #5694. |
2 months ago |
|
|
ff9a41871d |
feat(mysqlx): project per-session state into stats_mysqlx_processlist
stats_mysqlx_processlist had its DDL registered (mysqlx_admin_schema.cpp: kStatsMysqlxProcesslistTable) since the plugin-chassis merge but no writer ever existed. Operators querying the table got nothing back, no matter how many concurrent X-Protocol clients were connected. Issue #5691 caught this as the second of the two fully-empty stats surfaces. Wires the projection through the chassis ABI 3 register_runtime_view hook landed in PR #5688 — same pattern as the four runtime_mysqlx_<X> views, mirroring how core's stats___mysql_processlist re-projects on every admin SELECT. Three new pieces: 1. MysqlxSessionSnapshot struct (plugins/mysqlx/include/mysqlx_thread.h) captures one row's worth of session state: username, route name, worker_id, backend host:port, auth_mode, connection_state, and session_age_ms. bytes_in/bytes_out are reserved at 0; the per-route counters in MysqlxStatsStore aggregate by route, not by session, so filling these is P1 work. 2. Mysqlx_Thread::snapshot_sessions_for_stats(out, now_ms) walks sessions_ under sessions_mutex_ and appends one snapshot per active session. Lock scope is bounded (string copy + a few struct field reads); no I/O under the mutex; no cross-thread lock is held. Safe to call from any thread and intended for the chassis runtime-view refresh callback path. 3. mysqlx_populate_stats_processlist(SQLite3DB&) iterates over mysqlx_context().threads, gathers all snapshots into a flat vector under each thread's lock, then DELETEs and re-INSERTs the rows on statsdb. DELETE always runs (empty thread pool / empty session list both mean "no active sessions" — operators must see that, not stale state from the previous refresh). Wires three small read-only state observers on MysqlxSession (username_for_stats, route_name_for_stats, identity_for_stats, start_time_for_stats) so the snapshotter can read what it needs without needing friend-of-thread or exposing the session's mutable fields. These getters return values a debugger could already observe and cannot mutate the session. The refresh callback (refresh_stats_processlist_view in mysqlx_admin_schema.cpp) follows the same get_statsdb-via-services pattern as refresh_stats_routes_view, with one extra null-pointer guard: mysqlx_populate_stats_processlist is __attribute__((weak)) so the admin-schema unit test (which compiles mysqlx_admin_schema.cpp but doesn't link mysqlx_plugin.cpp) still links — the runtime null check is the safety net for that test build. Tests: relies on the existing fixture coverage in mysqlx_thread_unit-t and mysqlx_concurrent_unit-t to exercise the session-walk path without regression. A standalone snapshotter test would need to construct fake Mysqlx_Thread + MysqlxSession instances, which was scope-creep for P0 — defer to integration TAP coverage as the issue body recommends. All 9 affected unit suites stay green (255 assertions, no leaks introduced under ASAN). Known limitation, same as the runtime-view callback in the previous commit: SELECTs against the stats port (port 6032 with stats credentials) bypass the chassis dispatcher and return whatever's in the table since last refresh. Filing as a separate chassis-side follow-up. Refs: #5691. |
2 months ago |
|
|
04f771d2a3 |
feat(mysqlx): account per-route bytes_sent / bytes_recv on the data plane
The MysqlxRouteStats struct already had bytes_sent / bytes_recv atomic fields, but no setter and no caller — issue #5691 caught the third and fourth dead counters on the same dead-code list as conn_ok / conn_used. Adds two setters to MysqlxStatsStore: - record_bytes_sent(route, hg, n) - record_bytes_recv(route, hg, n) Both fetch_add(n, relaxed) under the existing mutex, with a 0-arg fast path that skips the lock entirely (frame.size() == 5 means a header- only frame and is the natural early-out — no underflow guard needed on `size - 5` because the call sites only fire when size > 5). Wires them at the two natural data-plane sites: - MysqlxSession::forward_to_backend — bytes_sent is "X-Protocol payload bytes the proxy forwarded from client to backend", measured as `frame.size() - 5` (strip the 5-byte X-Protocol frame header so the counter tracks operator-meaningful query/CRUD bytes rather than wire-level total). - MysqlxSession::handler_waiting_server_msg — bytes_recv is the same payload-only measure for the backend → proxy → client leg. NOTICE frames are counted (they're part of the data plane the operator paid for forwarding); the OK / EXECUTE_OK / FETCH_DONE family contributes 0 because their payloads are typically empty. Both counters need the route name available at the I/O site without re-reading identity_->default_route per frame. Caches that string into MysqlxSession::route_name_ at the resolve_backend_target() success tail and clears it in init() and reset(). The dataplane sites read route_name_ directly. (identity_ would not be null at those sites in practice — resolve_backend_target() is gated on a populated identity — but route_name_ avoids the optional-deref entirely and side-steps any future code path that nulls identity_ in-flight without a corresponding state transition.) Tests: extends mysqlx_stats_unit-t.cpp to plan(26), adding 4 assertions covering the new bytes_sent / bytes_recv accumulation through flush_to_sqlite, including the 0-arg no-op invariant and that destination_hostgroup is carried through. Refs: #5691. |
2 months ago |
|
|
e535a66ee1 |
fix(mysqlx): enforce per-user require_tls and allowed_auth_methods
Two MysqlxResolvedIdentity fields were loaded from runtime_mysqlx_users
into the in-memory store but never consulted by the auth path:
identity_->require_tls // per-user "must be over TLS"
identity_->allowed_auth_methods // per-user mechanism whitelist
Net effect:
- require_tls=1 did not reject MYSQL41-over-plaintext. Operators
setting this column expected a hardening guarantee they did not
actually get; an attacker bypassing TLS could still authenticate
via MYSQL41 (which is challenge-response over plaintext, but
leaks the user's full SHA1(SHA1(pw)) on a passive eavesdrop and
is replayable for the duration of the auth_challenge_).
- allowed_auth_methods='MYSQL41' did not reject PLAIN attempts
(and vice versa). Operators expected to be able to lock down a
user to a specific mechanism; the column was decorative.
# Fix
New helper MysqlxSession::enforce_identity_policy() called from both
auth resolution sites (handle_auth_plain at line ~539, handler_auth_
challenge_sent at line ~664) immediately after identity_ resolves
and before credential verification. It:
- rejects with 1045 "User requires a TLS connection" when
identity_->require_tls && !client_ds_.is_encrypted().
- rejects with 1045 "Authentication mechanism not allowed for
user" when identity_->allowed_auth_methods is non-empty and
auth_method_ is not in the comma-separated list (case-
insensitive, whitespace-trimmed).
Empty allowed_auth_methods preserves the historical "any wired
method" default so existing rows don't require a backfill — the
column already defaulted to '' in the table DDL.
# What this commit does NOT cover
backend_auth_mode is the third field in the same review finding
(reviewer's #2). It's partially implemented today by side-effect:
the backend_username being non-empty selects the service_account
codepath, empty selects mapped. The pass_through mode would require
forwarding the frontend AuthStart frame unmodified to the backend
and is not implemented; it remains TBD pending issue #5693
(asymmetric TLS / AsClient) which has overlapping protocol-shape
concerns. This commit deliberately scopes to the two policy checks
that are unambiguously broken; backend_auth_mode pass_through gets
its own commit when #5693 lands.
# Verified
21 / 21 mysqlx unit tests still green. Caught by an external review
pass, finding #2.
|
2 months ago |
|
|
4e32f44196 |
fix(mysqlx): backend TLS honors endpoint use_ssl flag
mysqlx_backend_endpoints.use_ssl is documented as forcing TLS on the
proxy↔backend connection regardless of frontend TLS state. The flag
was loaded into MysqlxBackendEndpoint at install_endpoints_from_admin
time (mysqlx_config_store.cpp::load_endpoint_overrides applies the
override on top of runtime_mysql_servers), but resolve_backend_target
then copied only hostname and mysqlx_port into the session's per-
target fields. The use_ssl flag was silently dropped.
Backend TLS at line 1186 was gated only on client_ds_.is_encrypted()
— frontend state. So:
- plaintext client + endpoint use_ssl=1 → backend was plaintext (BUG)
- TLS client + endpoint use_ssl=0 → backend was TLS (BUG, less
severe — the
client opted
into TLS but
the operator
chose not to)
The operator's intent (`mysqlx_backend_endpoints.use_ssl`) was simply
not honored.
# Fix
Capture ep.use_ssl into a new target_use_ssl_ session field at
resolve_backend_target. Reset it to false alongside target_address_/
target_port_ in the existing two reset paths.
At the backend-auth setup site, gate the SSL_CTX install on
`target_use_ssl_ || client_ds_.is_encrypted()`. Both signals
independently force backend TLS:
- target_use_ssl_ is the operator-mandated posture (network-zone /
compliance / cert-pinning concerns).
- client_ds_.is_encrypted() is the existing "match what the client
did" heuristic.
The two are AND-required together would have been wrong (operators
mandating backend TLS shouldn't have plaintext clients defeat it);
the OR is the right combination.
# Out of scope
- Asymmetric TLS / AsClient mode where the proxy mirrors the client's
choice on the backend with no operator override is tracked separately
in #5693.
- TLS passthrough (forward raw TLS records without decryption) is
#5692.
- The full multi-mode mysqlx_tls_backend_mode (DISABLED, PREFERRED,
REQUIRED, AS_CLIENT) is also #5693 — this commit just stops the
silent drop of the per-endpoint flag, restoring the documented
behaviour. Other modes remain TBD.
# Verified
21 / 21 mysqlx unit tests still green. Caught by an external review
pass, finding #3.
|
2 months ago |
|
|
becbf09ffa |
fix(mysqlx): plugin_descriptor visibility + admin_tables test + dead decl
Three issues code-review caught.
# Plugin-descriptor visibility
plugins/mysqlx/Makefile builds the .so with -fvisibility=hidden. The
loader resolves proxysql_plugin_descriptor_v1 via dlsym(); without an
explicit visibility-default override on this single exported function,
the symbol stays hidden and ProxySQL_PluginManager::load() fails with
"undefined symbol: proxysql_plugin_descriptor_v1". Add the
__attribute__((visibility("default"))) override so the .so exports
exactly one symbol — the descriptor entry point — and no others.
Latent regression unblocked here (test_mysqlx_plugin_load-t was also
affected).
# test_mysqlx_admin_tables-t.cpp rewrite
This integration-style TAP test (registered as unit-tests-g1,
@proxysql_min_version:4.0) used to assert on runtime_mysqlx_<X>
contents after each LOAD/SAVE call:
mgr.dispatch_admin_command(ctx, "LOAD MYSQLX USERS TO RUNTIME", result);
ok(admin_db.return_one_int("SELECT COUNT(*) FROM runtime_mysqlx_users") == 1, ...);
Under the new architecture, LOAD updates MysqlxConfigStore and never
writes runtime_mysqlx_users. The runtime view is repopulated only via
the chassis pre-SELECT hook in ProxySQL_Admin::GenericRefreshStatistics
— a path the test bypasses by reaching admin_db directly. The test
also seeded only mysqlx_users without seeding runtime_mysql_users,
so the cross-module-join in install_users_from_admin would silently
drop every user. And the SAVE-after-modifying-runtime tests were
asserting on a no-op (SAVE no longer reads runtime_mysqlx_users).
Rewrite:
- LOAD assertions now go via rows_affected from the callback and
DELETE editable + SAVE roundtrip to confirm the in-memory store
really held the rows. If SAVE re-materialises the rows the
operator deleted from the editable table, they came from the
store — exactly the contract LOAD is meant to install.
- Cross-module canonical tables seeded explicitly: helper
seed_canonical_tables creates runtime_mysql_users +
runtime_mysql_servers via the canonical
ADMIN_SQLITE_RUNTIME_MYSQL_USERS / ADMIN_SQLITE_TABLE_RUNTIME
_MYSQL_SERVERS DDL.
- SAVE-after-runtime-mutation tests (lines 263+) rewritten:
instead of "modify runtime_mysqlx_users, then SAVE, expect change
in mysqlx_users", they affirmatively assert the new contract —
runtime-view edits do NOT propagate to mysqlx_users on SAVE.
- backend_auth_mode values normalised to canonical enum values
(service_account, pass_through, mapped) since
mysqlx_backend_auth_mode_from_string collapses unknowns to
`mapped` and would have hidden any round-trip bug otherwise.
45 / 0 asserts after rewrite.
# Dead declaration
MysqlxConfigStore::rebuild_hostgroup_endpoints_locked() declared in
the header but never defined or called. Drop it. Code review found
this in the same pass.
|
2 months ago |
|
|
b4127156ed |
fix(mysqlx): listener reconciler reads MysqlxConfigStore, not runtime view
Code-review finding on PR #5688. mysqlx_reconcile_listeners_impl in plugins/mysqlx/src/mysqlx_listener_reconcile.cpp was still issuing a SELECT against runtime_mysqlx_routes to build its desired route set: SELECT name, bind FROM runtime_mysqlx_routes WHERE active=1 After the previous commit decoupled module state from the runtime_mysqlx_<X> projection, that table is empty until an admin SELECT triggers the projection callback. The reconciler runs from two non-admin paths: - mysqlx_plugin.cpp::mysqlx_start() at process startup, after install_routes_from_admin populates the store - load_routes_to_runtime() admin command, after install_routes_from_admin populates the store Both paths fire BEFORE any admin SELECT has projected the runtime view, so the reconciler would see ZERO desired routes. Net effect in production: no mysqlx listeners bind on startup, and LOAD MYSQLX ROUTES TO RUNTIME silently removes any listeners that were already mapped (the reconciler treats every mapped route as "no longer desired"). The unit tests didn't catch this because the weak symbol mysqlx_reconcile_listeners is null in unit-test binaries (the listener-reconcile pure variant is the one tested, and previously it was reading a pre-populated SQLite table). # Fix Read the desired route set from MysqlxConfigStore directly via a new public method: std::vector<std::pair<std::string,std::string>> MysqlxConfigStore::snapshot_active_routes() const It returns a (name, bind) pair for every active route under a shared lock. The reconciler consumes the snapshot the same way the previous SQL-driven version consumed result rows. The pure variant signature changes from void mysqlx_reconcile_listeners_impl(SQLite3DB& admindb, ...) to void mysqlx_reconcile_listeners_impl(const MysqlxConfigStore&, ...) which both restores the function's "no global state" purity (it no longer needs to reach into mysqlx_context() and never has) and makes the data-flow obvious: the store is the input. The strong weak-symbol entry mysqlx_reconcile_listeners(SQLite3DB&) is unchanged externally — it still takes admindb (now ignored, kept for ABI stability) and grabs the store from mysqlx_context() internally. Doc comment on the weak hook in include/mysqlx_plugin.h updated to describe the new data flow and to spell out explicitly why we do NOT read runtime_mysqlx_routes. # Test updates The two robustness tests that exercise the reconciler (test_listener_reconciliation, test_listener_reconciliation_bind_ change) used to set up a SQLite admindb with runtime_mysqlx_routes rows. They now construct a MysqlxConfigStore directly and populate it via install_for_test() — a smaller, more honest fixture that matches what the reconciler actually consumes. Note: MysqlxConfigStore is non-copyable/non-movable, so the bind-change test installs into two stack-local stores back-to- back via a helper lambda rather than rebuilding by value. # Status - All 21 mysqlx unit tests still green (646 ok, 0 not_ok). - Listener reconciler now sees the right state in both startup and LOAD MYSQLX ROUTES TO RUNTIME paths. Closes the BLOCKER from the PR #5688 review. Other concerns the review surfaced were all dismissed as not-bugs (ABI 1/2/3 loader compat, sql_references_table_ci whole-identifier match, lock ordering, sqlite_quote correctness, save semantics matching the canonical pattern). |
2 months ago |
|
|
9da7300afe |
fix(mysqlx): Admin/module separation for runtime config tables
Previously, plugin-chassis stored authoritative mysqlx runtime state inside admin-db tables (runtime_mysqlx_users / _routes / _backend_ endpoints / _variables). LOAD/SAVE commands shuffled rows between mysqlx_<X> and runtime_mysqlx_<X> via plain INSERT ... SELECT, and MysqlxConfigStore::load_from_runtime read the runtime_<X> tables back out into its in-memory map. The data lived in three places (editable mysqlx_<X>, persistent runtime_mysqlx_<X>, in-memory store) with no detection of skew between them. Issue #5687. The canonical pattern (mysql_users / GloMyAuth / runtime_mysql_users in lib/ProxySQL_Admin.cpp::__refresh_users / save_mysql_users_runtime _to_database) keeps Admin and the module strictly separated: Admin owns the editable configuration table and provides a runtime_<X> view of module state Module owns runtime state in its own data structures runtime_<X> is rebuilt on demand from module state, not persistent storage This commit restructures the mysqlx plugin to match. # MysqlxConfigStore: per-entity install / save / project triplets Replaces the monolithic load_from_runtime() with three independent operations per entity (users / routes / endpoints / variables): install_<X>_from_admin(db, err) LOAD <X> TO RUNTIME path. SELECT the editable mysqlx_<X> table (and the cross-module runtime_mysql_users / runtime_mysql_servers projections where applicable), build a new local representation, atomically swap into the in-memory store under the store's mutex. save_<X>_to_admin_table(db) SAVE <X> [FROM RUNTIME] TO MEMORY path. Mirror save_mysql_ users_runtime_to_database(false): mark all rows in mysqlx_<X> inactive, then upsert the live store contents with active=1. Inactive rows the operator deactivated but didn't delete are preserved. project_<X>_to_runtime_view(db) Runtime-view refresh path invoked by the chassis before any admin SELECT touches runtime_mysqlx_<X>. Mirror save_mysql_ users_runtime_to_database(true): DELETE the projected table, then INSERT live store contents. install_all_from_admin() is a convenience wrapper that runs all four in sequence; production code calls the per-entity methods so each LOAD command only touches its own slice of state, and unit tests have a single entry point that exercises the whole pipeline. # MysqlxConfigStore data-model additions - MysqlxResolvedIdentity gains `comment` (preserved through round- trip; the canonical mysql_users path also preserves comments). - MysqlxRoute gains `comment` for the same reason. - New public MysqlxBackendEndpointOverride struct (replaces the file-local MysqlxEndpointOverride that used to be in the .cpp). - New endpoint_overrides_ map: per-(hostname,mysql_port) overrides preserved verbatim across LOAD calls so SAVE can round-trip and so the runtime-view projection can faithfully reflect what was loaded. Previously these overrides were dropped after being folded into hostgroup_endpoints_. # Plugin: register_runtime_view + rewritten LOAD/SAVE callbacks In mysqlx_admin_schema.cpp: - Removes copy_table() and reload_config_store(); they were the INSERT...SELECT shovel between editable and runtime tables that encoded the architectural mistake. - Each load_<X>_to_runtime callback now calls install_<X>_from_ admin(*ctx.admindb, err) and never touches runtime_mysqlx_<X>. - Each save_<X>_from_runtime callback now calls save_<X>_to_admin_ table(*ctx.admindb) and never reads runtime_mysqlx_<X>. - rows_affected on LOAD now reports the active row count in the editable table (the source); on SAVE it reports the row count in the editable table after the dump (the destination). - Adds four refresh_<X>_runtime_view free functions and registers them via services.register_runtime_view() during schema registration. The chassis invokes these before any admin SELECT against the projected table. In mysqlx_plugin.cpp::mysqlx_start(): - Drops copy_to_runtime() entirely (the old "copy editable mysqlx_<X> to runtime_mysqlx_<X> at startup" step that no longer has a purpose). - Replaces the single load_from_runtime call with the four install_<X>_from_admin calls so a failure in one entity is surfaced individually in the log. - Keeps sync_disk_to_memory() unchanged. Disk-tier persistence is legitimate admin behaviour; only the runtime-tier copy was wrong. # Net effect at the SQL level - INSERT INTO runtime_mysqlx_<X> ... no longer happens on any LOAD or SAVE. The only writes to runtime_mysqlx_<X> are the on-demand projections, triggered by the chassis pre-SELECT refresh path. - SELECT FROM runtime_mysqlx_<X> always reflects current MysqlxConfigStore state, even if the operator never ran LOAD since the last edit to mysqlx_<X>. - mysqlx_<X> remains the editable table the operator writes to. The bug-stay-out-of-runtime contract documented in include/ProxySQL_Plugin.h (the rewritten doc block) now matches what this plugin actually does. |
2 months ago |
|
|
2b0c2fdcc4 |
fix(mysqlx): four minor security findings from issue #5676
Addresses the four Minor / Nit findings deferred when the Important
re-auth bug was fixed in commit
|
2 months ago |
|
|
55e90d1a76 |
fix(plugin-chassis,mysqlx): chassis read-path scaling, graceful shutdown, hardening
Six independent items from the independent review of PR #5651, batched together because each one alone is small. 1) lib/ProxySQL_PluginManager.cpp: replace g_active_plugin_manager_mutex with a std::shared_mutex. Readers (dispatch_admin_command, dispatch_query_hook, resolve_alias_to_canonical) take a shared lock so multiple worker threads can be inside plugin callbacks concurrently; writers (publish/unpublish in load/stop) take the unique lock. The previous std::mutex serialized every plugin-callback dispatch on one mutex. Once a plugin actually wires a query hook into MySQL_Thread / PgSQL_Thread, every concurrent client query on every worker would have queued behind that mutex — silently negating ProxySQL's per-worker parallelism. The lock-free proxysql_has_configured_plugin_query_hook() didn't help, since the actual dispatch still took the lock. Switching to shared_mutex on the read path lets dispatch scale linearly. 2) plugins/mysqlx/include/mysqlx_session.h + plugins/mysqlx/src/mysqlx_session.cpp + plugins/mysqlx/src/mysqlx_thread.cpp: add MysqlxSession::shutdown_notify_client() and call it from Mysqlx_Thread::run() on the way out of the worker loop. Previously Mysqlx_Thread::stop() flipped running_=false and joined. The destructor then deleted sessions, closing their fds. Connected clients saw an unannounced TCP RST mid-response and a torn TLS record. Now the worker, on its way out, walks sessions_ and for each live session: enqueues a Mysqlx::Error frame (code 1053, "Server is shutting down", FATAL severity); flushes one write_to_net pass; if TLS is up, calls SSL_set_quiet_shutdown(1) + SSL_shutdown so the peer's TLS stack sees a proper close_notify rather than a torn record. Best-effort throughout — never blocks on unresponsive peers because the process is exiting. 3) plugins/mysqlx/include/mysqlx_session.h + plugins/mysqlx/src/mysqlx_session.cpp: remove the dead TLS_PASSTHROUGH enum value and the two corresponding branches. handler_tls_accept_init's first three lines and the `tls_mode_ != TLS_PASSTHROUGH` predicate in handler_connecting_server only ran when set_tls_mode(TLS_PASSTHROUGH) had been called, which never happened in production — the `mysqlx_tls_mode` config column is never plumbed into a session. Worse, the PASSTHROUGH branch did not actually implement an opaque pipe (it just skipped TLS termination and resumed clear-text X-Protocol parsing, which would desync any real client). Drop the value rather than carry a misleading enum that suggests a feature exists. Future passthrough work should reintroduce a properly-wired implementation. 4) plugins/mysqlx/src/mysqlx_connection.cpp: check the return value of inet_pton in start_connect; fail fast on anything that isn't a valid IPv4 dotted-quad. Previously the return was discarded. inet_pton on a hostname (or IPv6 literal, or empty string) silently left sin_addr at 0.0.0.0 — producing a connect to 0.0.0.0/INADDR_ANY rather than the intended target. Real footgun because mysqlx_backend_endpoints.hostname accepts arbitrary strings. Now: fail with ERROR_STATE so the misconfig surfaces instead of routing traffic to the wrong target. Hostname resolution is still the upstream pipeline's job; start_connect deliberately stays narrow. 5) plugins/mysqlx/src/mysqlx_data_stream.cpp: move do_ssl_handshake's 64 KiB scratch buffer from the stack to a thread_local static. ASan-instrumented builds and large-thread-pool configs can run with thread stacks tight enough that a stack-allocated 64 KiB local straddles the limit. Each Mysqlx_Thread owns its own thread_local instance so the buffer is not shared between threads. 6) plugins/mysqlx/src/mysqlx_thread.cpp: document the listener-removal semantics on remove_listener_for_route. Document that already-accepted sessions on a listener that's being removed continue running against their existing target_hostgroup_ / target_address_ / target_port_ until they finish or hit idle timeout. That matches surrounding MySQL behaviour (DROP TABLE doesn't cancel in-flight queries; ALTER doesn't kick off open prepared statements). Future change can call shutdown_notify_client on matching sessions if active disconnection becomes desirable. NOT changed: the agent-flagged "compression overshoot" issue at plugins/mysqlx/src/mysqlx_session.cpp:1283. The zstd-stream loop already caps the resize at `cap` on line 1369 (`if (new_sz > cap) new_sz = cap;`) before the resize, so decompressed never grows past the cap; there is no overshoot. Verified by tracing the loop. Skipped. Verified locally: - plugin .so builds clean with PROXYSQL40=1 and the implied tier flags. - libproxysql.a and src/proxysql build clean. - plugin chassis tests (plugin_lifecycle_unit-t, plugin_dispatch_unit-t, plugin_manager_unit-t, plugin_query_hook_unit-t) build and pass with the new shared_mutex read path. plugin_manager_unit-t shows the same 2 pre-existing destructor-related failures it had before this commit (verified by stash + rebuild). - mysqlx_robustness_unit-t passes 74/74. - mysqlx_session_unit-t has the same pre-existing failures at 33-34. |
2 months ago |
|
|
04bccec51e |
chore(plugin-chassis): tighten gating, drop dead paths, gate forgery setters
Five clean-up items from the independent review of PR #5651. None of these change behaviour on a normal run; they each fix a concrete way the current code is misleading or unnecessarily exposed. 1) lib/ProxySQL_Admin.cpp: gate the three plugin-DB-handle getters (proxysql_plugin_get_admindb / _configdb / _statsdb) under #ifdef PROXYSQL40. Previously these were defined unconditionally and emitted symbols into v3.0/v3.1 binaries. The chassis is a v4.0 feature; the user explicitly required that v3.x builds carry no plugin-aware code. Wrap in PROXYSQL40 so they are entirely absent from v3.x linkage. ProxySQL_PluginManager.cpp's extern declarations (lines 23-25) are already inside the file-wide PROXYSQL40 gate and so resolve only when the gate is active. 2) lib/ProxySQL_PluginManager.cpp + include/ProxySQL_PluginManager.h: delete the dead `#else /* !PROXYSQL40 */` branches. Both files are wrapped in a top-level `#ifdef PROXYSQL40` covering the entire body. Inner `#ifdef PROXYSQL40 ... #else ... #endif` blocks therefore had unreachable `#else` arms — 30+ lines of "pre-chassis two-phase loader" in the .cpp, plus a redundant declaration of proxysql_load_configured_plugins in the .h. The dead arms read as load-bearing alternative implementations on review and that is exactly the wrong signal. Drop them; the single PROXYSQL40 path is the only one. 3) lib/Admin_Bootstrap.cpp + include/proxysql_admin.h + src/main.cpp: remove ProxySQL_Admin::materialize_plugin_tables(). `Admin::init()` already merges plugin-declared schemas into tables_defs_{admin,config,stats} (~line 944) and runs the DDL via check_and_build_standard_tables() (~line 994), all on the same first-boot/reload code path the core tables use. The follow-up call to GloAdmin->materialize_plugin_tables() in main.cpp ran a second name-dedup pass that found everything already present and produced an empty new-rows set — i.e. the post-init helper was a no-op disguised as load-bearing infrastructure. Delete the helper, the header declaration, and the main.cpp call site, and update the comments in main.cpp + ProxySQL_PluginManager.cpp to point at Admin::init() as the single canonical materialization site. Leave a NOTE in Admin_Bootstrap.cpp at the old call site so anyone re-adding a similar helper sees why the prior one was removed. 4) plugins/mysqlx/include/mysqlx_session.h + plugins/mysqlx/src/mysqlx_session.cpp: gate the two genuine forgery vectors behind MYSQLX_TEST_BUILD. inject_identity_for_test() bypasses the full auth flow — no credential check, no capability negotiation, just sets identity_ to a caller-supplied MysqlxResolvedIdentity. resolve_backend_target_ for_test() drives a private routing helper without an authenticated identity. Both are necessary for unit tests but should not be reachable at all in shipped binaries; an in-process exploit reaching the session can call inject_identity_for_test() to forge an authenticated identity. Wrap them in #ifdef MYSQLX_TEST_BUILD; define -DMYSQLX_TEST_BUILD in the test Makefile only. The remaining target_*_for_test() getters are read-only state observers and are left unconditional — they cannot mutate the session and a debugger could observe the same state regardless. 5) test/tap/tests/unit/Makefile: define -DMYSQLX_TEST_BUILD on every unit test compile line via OPT. This is the test-only knob that re-enables the gated forgery methods so unit tests still compile. plugins/mysqlx/Makefile does NOT define this macro, so the production .so does not compile in the entry points. Verified locally: - plugins/mysqlx/ProxySQL_MySQLX_Plugin.so builds clean with PROXYSQL40=1 PROXYSQL31=1 PROXYSQLFFTO=1 PROXYSQLTSDB=1 PROXYSQLGENAI=1 (no MYSQLX_TEST_BUILD). - test/tap/tests/unit/mysqlx_robustness_unit-t builds and runs: 74/74 assertions pass, including the ones that exercise inject_identity_for_test (visible only because the test Makefile defines MYSQLX_TEST_BUILD). |
2 months ago |
|
|
4bd4b462be |
fix(mysqlx): three blocking protocol/pool correctness bugs
Three independent issues, fixed together because they all sit on the
data-plane critical path and each one alone is enough to corrupt the
session state machine.
1) MysqlxConnection::reset() did not scrub backend_ds_ buffers.
The pooled-connection reuse path (Mysqlx_Thread::return_connection_
to_cache → reset → next session's get_connection_from_cache) calls
MysqlxConnection::reset() between sessions. The previous version of
reset() cleared in_transaction_, has_prepared_stmt_, reusable_, and
auth_state_ but left the underlying MysqlxDataStream's read_buf_,
write_buf_, ssl_write_buf_, and complete_frames_ untouched. Any
straggler frame the prior session left in flight — for example a
NOTICE that arrived after the terminal frame, an unread row that
the prior session abandoned, or a half-parsed frame whose body had
not finished arriving — would be served to the next session as if
it were the response to its first query. That is a real
cross-session data leak, not just a robustness issue.
Adds MysqlxDataStream::clear_io_buffers() which scrubs the I/O
queues without touching fd_, ssl_, rbio_ssl_, wbio_ssl_, or
encrypted_. Preserving the SSL state is critical: rebuilding the
SSL* would force a fresh TLS handshake on every pool checkout and
would discard ALPN / cipher negotiation already done. Calls it
from MysqlxConnection::reset().
2) handler_capabilities_set silently accepted unparseable
CapabilitiesSet messages.
In plugins/mysqlx/src/mysqlx_session.cpp the previous logic was
`if (cap_set.ParseFromArray(...))` { ... } and on parse failure
fell through to the unconditional `pop_frame(); send_ok();
status_=CONNECTING_CLIENT;`. A buggy or hostile client that ships
an unparseable capability payload would be told the negotiation
succeeded — the server then proceeded with default capabilities
while the client believed it had selected something. This is the
exact kind of confused-state bug the X Protocol error codes exist
to prevent.
Returns send_error(5051, "Invalid CapabilitiesSet payload") on
parse failure. 5051 is the X Protocol convention for an
unrecognized/unparseable capability message; 5052 is reserved for
"supported capability with rejected value" and is already used
for the compression-value-not-supported path two blocks down.
3) step_auth_authenticate_start_sent ignored the return of
AuthenticateContinue::ParseFromArray.
In plugins/mysqlx/src/mysqlx_connection.cpp the backend-side auth
state machine called `cont.ParseFromArray(frame->data() + 5,
frame->size() - 5)` and discarded the bool. A backend (or a MITM
that has bypassed TLS, or a misbehaving X-Protocol-aware proxy
between us and the backend) returning a malformed
AuthenticateContinue would produce an empty auth_data() field;
the resulting empty challenge was then handed to
mysqlx_mysql41_scramble() which would compute a scramble against
a zero-length challenge — undefined-input territory. Backend
auth would still fail eventually, but only after operating on
uninitialized protobuf fields.
Now: on parse failure, mark BACKEND_AUTH_ERROR and return -1
so the failure surfaces immediately and we never trust the
half-parsed message body.
Verified: plugins/mysqlx builds cleanly with PROXYSQL40=1
PROXYSQL31=1 PROXYSQLFFTO=1 PROXYSQLTSDB=1 PROXYSQLGENAI=1.
Existing unit tests for protocol parsing and auth state are
unchanged in expected behaviour (the new code paths trip only on
malformed inputs that no current test exercises).
|
2 months ago |
|
|
aef01ef0be |
feat(mysqlx): compress outbound server frames (Phase 3)
Phase 3 of three-phase X Protocol compression support: server→client
compression on outbound frames. Wraps up the MVP — CapabilitiesSet
negotiation (Phase 1), inbound decompression (Phase 2), and now
outbound compression all roundtrip.
What this commit does
=====================
forward_frame_to_client() — the chokepoint for backend→client frame
forwarding — now routes through send_to_client_compressed(), which:
1. Returns early to plain client_ds_.enqueue_frame() when compression
is not negotiated OR the body is below COMPRESSION_MIN_OUTPUT_BYTES
(50 bytes — same threshold the upstream MySQL X plugin uses; per-
message envelope + framing overhead would otherwise dwarf savings).
2. With combine_mixed_messages = false: emits one Compression frame
per body. The protobuf has server_messages set to the original
frame's msg_type and uncompressed_size set to the original body
length, so a spec-compliant client can decompress in place.
3. With combine_mixed_messages = true: appends a fully-framed copy
of the body to compress_batch_framed_ and bumps compress_batch_count_.
Once the count reaches max_combine_messages (defaulting to 64 if
the client didn't supply one), or flush_compression_batch() is
invoked at a natural boundary, all buffered frames are emitted as
one Compression message with NEITHER server_messages nor
client_messages set — payload is the concatenated stream of framed
X messages, matching the spec's third payload shape.
The flush points are:
- handler_waiting_server_msg() when it sees a terminal frame (end
of a result set / final OK / etc.) — the response is "done" so
anything still buffered must reach the wire before we go back to
WAITING_CLIENT_XMSG.
- handler_session_reset_waiting() on the ERROR path before
write_to_net(), same reasoning.
Mid-response we deliberately do NOT flush — that would defeat the
combine_mixed_messages benefit by emitting a Compression message per
batch hit, which is exactly what we're trying to avoid for streaming
result sets. The count cap bounds how long a single batch can grow.
Compressor selection mirrors Phase 2:
- zstd_stream uses ZSTD_compressCCtx() with a per-session ZSTD_CCtx
that's lazily allocated and freed in reset_compression_state().
- lz4_message uses LZ4_compress_default() one-shot.
On compressor failure (ZSTD_isError or lz4 returning <= 0) the helper
falls back to enqueueing the body uncompressed — losing compression
benefit beats dropping the message. The session itself stays healthy.
Tests
=====
mysqlx_compression_unit-t now plans 64 sub-tests, all passing:
- Phase 1: 22 sub-tests for capability negotiation
- Phase 2: 17 sub-tests for inbound decompression
- Phase 3 (new, 25 sub-tests):
- zstd round-trip: frame on the wire is COMPRESSION with
server_messages = NOTICE; payload decompresses back to the
exact original 200-byte body
- lz4 round-trip, same shape
- below-threshold passthrough: 20-byte body emitted as plain
NOTICE, not COMPRESSION (verifies the fast path)
- combine_mixed_messages with max_combine_messages=3: three
successive sends produce ONE Compression frame whose
decompressed payload contains all three NOTICE bodies in
order, with neither server_messages nor client_messages set
- compression-disabled passthrough: when no negotiation, the
helper acts as a plain enqueue_frame() (proves a client that
never opts in is unaffected)
A few new test-only accessors expose the batch state and
send_to_client_compressed() entry point so tests can exercise the
output path without wiring a fake backend (which would block on
connect() in the dispatcher anyway).
Verification
============
Top-level `PROXYSQLGENAI=1 make` builds cleanly. mysqlx_compression_unit-t
passes 64/64. plugin_lifecycle_unit-t (26/26) passes unchanged.
mysqlx_session_unit-t still has its two pre-existing failures at
sub-tests 33-34 (auth flow), unrelated to compression and present on
this branch before the Phase 1 commit.
|
2 months ago |
|
|
b1fd6b31fc |
feat(mysqlx): decompress incoming Compression messages (Phase 2)
Phase 2 of three-phase X Protocol compression support: client→server
decompression. Compression on the server→client path (Phase 3) still
goes out uncompressed; CapabilitiesSet stores the negotiation in
Phase 1 and this commit consumes it.
What this commit does
=====================
When the client sends a Mysqlx.Connection.Compression message AND
compression has been negotiated, we now:
1. Parse the Compression protobuf — payload bytes, optional
uncompressed_size hint, optional client_messages tag.
2. Decompress the payload using the negotiated algorithm:
- zstd_stream: streaming decompression via a ZSTD_DCtx that
persists across messages on the same session (per spec —
successive Compression frames may continue a single zstd
stream, so we cannot recreate the context per frame).
- lz4_message: one-shot LZ4_decompress_safe. The X spec defines
lz4_message as one independent frame per message, so no
persistent context is needed.
3. Feed the decompressed bytes back into client_ds_.feed_bytes() so
the existing frame parser picks them up. Two payload shapes per
spec are handled:
- client_messages set: payload is one decompressed body of that
type — we re-frame with a 5-byte X header in front.
- neither set: payload is already a sequence of fully-framed
X messages — fed verbatim.
4. The dispatch loop re-enters on the same handler tick (to_process
set), so a Compression-wrapped StmtExecute runs end-to-end without
an extra network roundtrip.
If client_messages is unset and server_messages IS set, the message
is rejected (5008): server_messages on the c→s path is always wrong.
Anti-bomb / bounds
==================
COMPRESSION_MAX_DECOMPRESSED_BYTES = 16 MiB caps how much output a
single Compression message can produce, mirroring MysqlxDataStream's
existing on-the-wire X_MAX_PAYLOAD_SIZE so a Compression frame never
expands beyond what the rest of the data plane can handle anyway. If
the client provides an uncompressed_size hint smaller than 16 MiB we
honor that as the tighter bound. Hint of 0 with non-empty payload is
treated as malformed.
For zstd, the decompression loop re-checks the cap before each
ZSTD_decompressStream call and bails (with 5008) the moment the
output buffer would exceed it. A no-progress condition (zout.pos == 0
with input remaining) also bails — that catches malformed streams
that would otherwise spin forever.
For lz4, LZ4_decompress_safe(dstCapacity = cap) naturally enforces
the limit: the decompressor refuses to write past dstCapacity and
returns an error code we map to 5008.
Linkage / build
===============
The plugin .so is dlopen'd with RTLD_LOCAL, so symbols from libzstd
/ liblz4 that the proxysql binary may already pull in are NOT
visible to the plugin. This commit links the static
deps/zstd/zstd/lib/libzstd.a + deps/lz4/lz4/lib/liblz4.a archives
directly into the .so so the plugin is self-contained.
The session header forward-declares ZSTD_DCtx / ZSTD_CCtx so the
zstd headers don't leak through include/mysqlx_session.h into other
translation units.
Tests
=====
mysqlx_compression_unit-t now covers Phase 2 end-to-end (39 sub-
tests total, all passing):
- Phase 1: capability negotiation (22 sub-tests, unchanged)
- decompress zstd_stream client_messages=SQL_STMT_EXECUTE,
inner StmtExecute reaches dispatch (no 5008, session moves
past WAITING_CLIENT_XMSG)
- same for lz4_message
- oversize bomb attempt: lie about uncompressed_size, send
a 1 MiB-of-zeros payload — rejected with 5008
- garbage compressed payload: rejected with 5008
- sanity: COMPRESSION before negotiation still 5008
Verification
============
Top-level `PROXYSQLGENAI=1 make` builds cleanly; the plugin .so
links against the static zstd + lz4 archives. plugin_lifecycle_unit-t
(26/26) passes unchanged. mysqlx_session_unit-t still has its two
pre-existing failures at sub-tests 33-34, identical to behavior
before this commit (verified by stashing only the Phase 2 edits to
plugins/mysqlx/{src,include}/* + Makefile and rebuilding). The
mysqlx_thread_unit-t / mysqlx_concurrent_unit-t hangs are also
pre-existing and reproduce on the unmodified branch.
|
2 months ago |
|
|
f19be5f3a0 |
feat(mysqlx): negotiate X Protocol compression capability (Phase 1)
Phase 1 of three-phase X Protocol compression support: capability
negotiation only. The COMPRESSION message itself is still rejected
with error 5008 in dispatch_client_message(); Phase 2 wires up
decompression on input and Phase 3 wires up compression on output.
What this commit does
=====================
- send_capabilities() now advertises a `compression` capability listing
the algorithms the plugin can support: zstd_stream and lz4_message.
Both libraries (libzstd, liblz4) are already statically linked into
libproxysql.a / pulled into the unit-test link line, so this does not
introduce any new runtime dependency.
- handler_capabilities_set() detects when a client sets the `compression`
capability and parses the OBJECT value's sub-keys:
- `algorithm` (required string) — must match an advertised value;
anything else is rejected with X-Protocol error 5052 (the
capability-prepare-failed code per the spec).
- `server_combine_mixed_messages` / `combine_mixed_messages` (bool)
- `server_max_combine_messages` / `max_combine_messages` (uint)
Both spelling variants are accepted because mysql-connector-python
emits the short form while libmysqlclient emits the spec form, and
the upstream MySQL server tolerates both.
- Negotiation outcome is stored on MysqlxSession via three new members
(compression_algo_, compression_combine_mixed_messages_,
compression_max_combine_messages_). Phase 2 / Phase 3 will read
these to drive (de)compression. They are reset by both init() and
reset() so a session reuse does not inherit stale negotiation.
- Unsupported algorithms (e.g. deflate_stream) and structurally
malformed values (wrong protobuf type, missing algorithm field) are
rejected with a non-fatal X-Protocol Error frame — the session
remains healthy so the client can either retry CapabilitiesSet or
proceed without compression.
Why 5052 (non-fatal) and not 5008
---------------------------------
5008 is the runtime "compression message arrived but compression is
disabled" error and stays in dispatch_client_message() for now. 5052
is the spec-defined "capability prepare failed" status used during the
CapabilitiesSet handshake; treating an unknown algorithm as a
capability error matches the upstream MySQL X plugin's behavior and
lets compliant clients downgrade to no-compression on the same
connection.
Tests
=====
New unit test: test/tap/tests/unit/mysqlx_compression_unit-t.cpp
(22 sub-tests, all passing). Covers:
- CapGet response advertises `compression` with both algorithms
- CapSet zstd_stream + combine hints accepted, stored on session
- CapSet lz4_message accepted, stored on session
- CapSet deflate_stream rejected with 5052 (non-fatal), session
remains healthy with no algorithm set
- CapSet with wrong-shape compression value (scalar instead of
object) rejected with 5052
Existing mysqlx_session_unit-t / plugin_lifecycle_unit-t still pass
(the two pre-existing failures in mysqlx_session_unit-t at sub-tests
33-34, "auth succeeded for correct password" and "session in
WAITING_CLIENT_XMSG after auth", are present on this branch before
this commit and unrelated to compression — verified by stashing only
the mysqlx_session.{cpp,h} edits and re-running).
Build infra fix piggy-backed on this commit
-------------------------------------------
test/tap/tests/unit/Makefile referenced
$(SQLITE3_LDIR)/../libsqlite_rembed.a in the GenAI-tier link line,
but that .a is no longer produced on this branch (it was the artifact
of the now-removed sqlite-rembed Rust extension). Without removing
this stale reference, no plugin/mysqlx unit test can link, which
blocks verifying the new compression test alongside the existing
mysqlx tests required by the task. Mirrors the upstream fix that
already landed on sibling branches as commit
|
2 months ago |
|
|
79cac4c976 |
chore(mysqlx): retire MysqlxFrontendSession, MysqlxBackendSession, X_FAST_FORWARD
Three pieces of dead code that survived the MysqlxWorker retirement
(commit
|
2 months ago |
|
|
c723ede0cf |
Merge remote-tracking branch 'origin/plugin-chassis' into ProtocolX-rebased
# Conflicts: # lib/ProxySQL_PluginManager.cpp # plugins/mysqlx/Makefile # plugins/mysqlx/include/mysqlx_plugin.h # plugins/mysqlx/include/mysqlx_session.h # test/tap/groups/groups.json # test/tap/tests/test_mysqlx_listener_smoke-t.cpp # test/tap/tests/unit/Makefile |
2 months ago |
|
|
e462bb0cb4 |
fix: address PR review feedback
Resolves in-scope review items on PR #5651. Items deemed out of scope for this PR (O(N) admin dispatch lookup, per-plugin mutex, worker backpressure, runtime-reload DDL semantics, E2E tests going through ProxySQL rather than directly to MySQL) are tracked elsewhere; items that were already fixed in earlier commits on this branch (Admin_Handler MYSQLX alias vectors, Admin_Bootstrap PROXYSQL40 gating) are acknowledged as stale. Changes: * .github/workflows/CI-mysqlx.yml: add `include/` and `src/proxysql_global.cpp` to both `sparse-checkout` blocks. The plugin Makefile discovers the repo root via `src/proxysql_global.cpp` and includes headers from `include/`; without these, `make` inside `plugins/mysqlx` climbs up to `/` or fails on missing includes. (coderabbit: "Checkout the files required by the plugin Makefile.") * .github/workflows/CI-mysqlx.yml: `fail-on-cache-miss` now depends on `github.event_name == 'workflow_run'`. Strict behavior is retained for the cache-driven pipeline that CI-trigger populates; manual `workflow_dispatch` runs no longer fail when the SHA-specific cache doesn't exist yet. (coderabbit: "Don't make manual runs depend on a pre-existing build cache.") * plugins/mysqlx/Makefile: replace the unbounded `while [ ! -f ./src/proxysql_global.cpp ]; do cd ..; done` with a bounded upward walk (up to 12 levels) that fails fast with a clear error message when the marker file is missing — e.g. on a sparse checkout that omits `src/proxysql_global.cpp`. Build output no longer appears to hang indefinitely. (coderabbit: "Prevent root discovery from hanging on sparse or invalid checkouts.") * doc/PLUGIN_API.md: update startup sequence from the old three-phase (load → init → start) description to the four-phase chassis lifecycle (load → register_schemas → admin materialize → init → start). Document the `register_schemas` descriptor field and the `abi_version` / `PROXYSQL_PLUGIN_ABI_VERSION` contract. Clarify that `stop()` pairs with `init()` (not `start()`) for teardown symmetry. (coderabbit: "Update plugin lifecycle documentation to reflect four-phase model and ABI versioning.") * plugins/mysqlx/include/mysqlx_session.h: default-initialize every field in `MysqlxCredentials`. `bool x_enabled` was indeterminate for a default-constructed instance, which would let a lookup miss produce a value that silently authenticates. In-class initializers give every field a deterministic default. (coderabbit: "Default- initialize credential fields used by auth decisions.") * plugins/mysqlx/src/mysqlx_backend_session.cpp: close `backend_fd_` when `authenticate_backend()` returns false. Previously `connect()` stored the newly-opened fd in `backend_fd_` and returned the auth result; a retry on the same `MysqlxBackendSession` would then overwrite `backend_fd_` and leak the previous socket. (coderabbit: "Close/reset `backend_fd_` when authentication fails.") * lib/Admin_Bootstrap.cpp: `materialize_plugin_tables()` now runs DDL only for the subset of plugin-declared tables that were freshly inserted into `tables_defs_*` (the rest are already materialized). DDL failures are startup-fatal: on `execute()` returning false, log the plugin kind + table name + SQL and `exit(EXIT_FAILURE)`. A partially materialized schema produces opaque runtime errors from the plugin later on, so aborting startup is the right default. (gemini + coderabbit: "Execute DDL only for newly materialized plugin tables" / "add error checking".) Not changed in this commit (deferred / out of scope / stale): * CI-mysqlx E2E tests going through ProxySQL (requires plumbing listener + admin config in the CI workflow). * O(N) admin dispatch scan → hash map. * Descriptor build-id hash for libstdc++ compat detection. * Per-plugin mutex on mutable plugin context. * Worker client-fd enqueue backpressure. * Stale plan-doc feedback on docs/superpowers/plans/*.md. Verification (clean tree): * `unset PROXYSQL*; make cleanall && make debug -j$(nproc) && make build_tap_test_debug -j$(nproc)` -- 0 errors. * `PROXYSQLGENAI=1 make cleanall && PROXYSQLGENAI=1 make debug -j$(nproc) && PROXYSQLGENAI=1 make build_tap_test_debug -j$(nproc)` -- 0 errors. * Plugin unit tests (PROXYSQL40): 48 + 52 + 26 + 46 + 10 = 182 pass. |
2 months ago |
|
|
6fe50376d5 |
Merge remote-tracking branch 'origin/feature/mysqlx-route-identity' into HEAD
# Conflicts: # plugins/mysqlx/src/mysqlx_session.cpp # test/tap/tests/unit/Makefile # test/tap/tests/unit/mysqlx_robustness_unit-t.cpp |
2 months ago |
|
|
a2e99eed50 |
perf(mysqlx): only invoke handler() for sessions with real work
process_all_sessions previously forced sess->to_process=true on every tick and unconditionally called sess->handler(), burning CPU at large idle session counts (one full state-machine traversal per session per loop iteration, regardless of whether anything had changed). Now only call handler() when at least one of these is true: - a poll event landed on the client or server data stream - the session self-flagged to_process (handler wants to re-run) - a complete frame is already buffered on either stream Also make Mysqlx_Thread::sessions_mutex_ mutable and take it in get_session_count() const. Previously const accessors that needed to lock the mutex couldn't — and the session count was read without the lock at all, racing the writer that appends/removes sessions. |
2 months ago |
|
|
6a921514cc |
fix(mysqlx): protocol, data-stream and stats robustness fixes
mysqlx_connection.cpp:
Drain leading NOTICE frames in read_auth_frame() instead of returning
nullopt on the first NOTICE. MySQL backends commonly emit a
session-state-change notice before AuthenticateContinue or Ok, and
returning nullopt caused the auth state machine to spin on try-read
for the full 10s handshake timeout before completing. The two callers
(step_auth_capabilities_get_sent and step_auth_capabilities_set_sent)
now use the shared helper and drop their duplicated NOTICE checks.
Also added a frame-size guard before reading the message-type byte.
mysqlx_data_stream.{h,cpp}:
Add close_and_reset() which tears down SSL/BIO state and clears every
read/write buffer and parse flag without close()ing the fd. Required
by mysqlx_session.cpp's return_backend_to_pool(), where the fd is
owned by the pooled MysqlxConnection and must stay open after the
data stream is wiped. Fix SSL_read return handling: a 0-return is a
clean TLS shutdown (close_notify) and must surface as a connection
close, not as a WANT_IO/retry. The previous code treated 0 and <0
identically and would loop forever on a cleanly-closed TLS peer.
mysqlx_protocol.cpp:
mysqlx_build_frame now rejects serialized payloads at the uint32
boundary so the +1 for the message-type byte cannot wrap to 0. This
mirrors the X_MAX_PAYLOAD_SIZE clamp already applied by the inbound
parser in MysqlxDataStream.
mysqlx_stats.cpp:
Rewrite the stats_mysqlx_routes INSERT builder to use std::string
concatenation instead of a fixed 1024-byte snprintf buffer. Long
route names plus escaping could overflow the buffer and the row was
silently dropped without reaching the statsdb.
|
2 months ago |
|
|
d0f6d8e4a8 |
Merge remote-tracking branch 'origin/fix/mysqlx-listener-lifecycle' into HEAD
# Conflicts: # plugins/mysqlx/Makefile # plugins/mysqlx/src/mysqlx_plugin.cpp # test/tap/tests/unit/Makefile # test/tap/tests/unit/mysqlx_robustness_unit-t.cpp |
2 months ago |
|
|
fb1a0cd706 |
Merge remote-tracking branch 'origin/fix/mysqlx-backend-tls-post-auth' into HEAD
# Conflicts: # test/tap/tests/unit/mysqlx_robustness_unit-t.cpp |
2 months ago |
|
|
3d107c3bed |
chore: commit pre-existing plugin manager improvements
Improvements left uncommitted at the end of Step 0: - atomic g_active_plugin_manager pointer - reject plugin descriptors with null/empty name - assert single-threaded init phase - debug-log dispatched plugin commands - propagate mysqlx_register_admin_schema failure into init - additional plugin manager unit tests (init/start/stop fail, double load) - updated ABI doc/comments for table copy semantics, command context lifetime, services availability, and status_json storage |
2 months ago |
|
|
2ffa38bc6c |
fix(mysqlx): use resolved backend_username when setting up the backend connection
The session's backend-connection setup site in handler_connecting_server now falls back to identity_->backend_username when that field is non-empty, and only reuses the frontend username_ when it is empty (the mapped-mode / default case). coderabbitai flagged on PR #5645 (outside-diff; the code originates in PR #5641): the pre-fix setup call always passed the frontend username_ to set_backend_user() while pairing it with the resolved password from identity_->backend_password. For mysqlx_users rows whose backend_auth_mode is `service_account` and whose backend_username differs from the frontend username, this mismatch paired userA's password with userB's name — backend auth failed with access-denied even though both columns were internally consistent. The route-identity refactor (earlier commits on this branch) widened the user lookup to return the full MysqlxResolvedIdentity struct, which already carries backend_username. This commit wires that existing field through at the backend-setup site; no store-side changes are required. See MysqlxBackendAuthMode in mysqlx_config_store.h for the full set of modes and their semantics. Testing: two new unit assertions covering both branches of the selector — mapped mode (empty backend_username falls back to frontend username_) and service_account mode (distinct backend_username is used verbatim). Assertion count moves from 46 to 48. A test-only getter MysqlxConnection::get_backend_user_for_test() was added so the test can observe the string that was ultimately passed to set_backend_user. Out of scope: enforcement of MysqlxBackendAuthMode semantics (for example, rejecting service_account rows that omit backend_username at load time). That validation belongs with the config-store loader and is tracked separately. |
2 months ago |
|
|
c78d7b859c |
fix(mysqlx): reconcile bind-address changes, document single-admin-thread assumption
The listener reconciler built its desired snapshot keyed only by route name, so editing a route's `bind` column (e.g. from `:33061` to `:33062`) and running LOAD MYSQLX ROUTES TO RUNTIME left the old listener running and never opened the new port — the removal pass saw the name still in the desired set and skipped the route entirely. The removal pass now also compares the currently-bound `host:port` on the owning thread against the desired bind and treats any mismatch as a removal; the subsequent addition pass rebinds under the new spec. To support that comparison, `Mysqlx_Thread` now stores bind ports in a parallel `listener_ports_` vector alongside the existing `listener_addrs_` and exposes `get_listener_addr_for_route()` returning the canonical `"host:port"` form. Also added a comment documenting the pre-existing single-admin-thread invariant that justifies snapshotting the DB outside of `route_to_thread_mutex` (noted in code review, not a behavioral change). Testing: new unit-test assertions exercise a full bind-change reconcile end-to-end — pick two free ports, reconcile at port1, update the runtime row to port2, reconcile again, and assert the listener is now bound at port2 with total listener count still 1. 42 assertions pass (was 38). Out of scope: changing the concurrency model. The single-admin-thread assumption is pre-existing throughout ProxySQL admin execution. |
2 months ago |
|
|
dd131b0aa2 |
fix(mysqlx): reconcile listeners at startup and on LOAD ROUTES TO RUNTIME
Startup previously capped listener creation at `pool_size` routes via `ti < ctx.threads.size()` in mysqlx_plugin.cpp's startup loop, so any routes beyond the thread-pool size had no listener. Runtime route changes via `LOAD MYSQLX ROUTES TO RUNTIME` never touched the listener topology at all — adding, removing, or toggling a route's `active` flag had no effect until a full restart. Flagged in the ProtocolX code review as item #4. This commit: - Drops the `ti < pool_size` cap in the startup loop. - Distributes routes across threads round-robin (the original intent) using a shared plugin-scope `route_to_thread` map guarded by a mutex, with a `next_rr_index` cursor. - Adds route-name tracking to Mysqlx_Thread: a new parallel `listener_route_names_` vector alongside `listener_fds_` / `listener_addrs_`, plus a new `remove_listener_for_route(name)` method that closes the fd and prunes all three vectors. Returns true/false so callers can use it idempotently. - Adds a `mysqlx_reconcile_listeners(admindb)` desired-state reconciler. Reads active routes from `runtime_mysqlx_routes`, binds missing listeners round-robin, and removes listeners for routes that are gone or deactivated. Startup and `LOAD MYSQLX ROUTES TO RUNTIME` both go through this single path, so both agree. The reconciler is idempotent: re-running with the same desired set is a no-op. - The reconciliation core lives in a new file `plugins/mysqlx/src/mysqlx_listener_reconcile.cpp` as a pure helper `mysqlx_reconcile_listeners_impl(...)` taking state by parameter, so unit tests can drive it against a minimal fake context. The convenience wrapper that reads `mysqlx_context()` lives in plugin.cpp and is declared weak so tests of admin_schema.cpp that don't link plugin.cpp still link cleanly (admin_schema null-checks the weak pointer). Tests: mysqlx_robustness_unit-t grows from 33 to 38 assertions — four thread-API tests (add_listener with route name on two threads, remove_listener_for_route removes and is idempotent) plus one integration test that builds an in-memory admin DB with one route and asserts `mysqlx_reconcile_listeners_impl` binds exactly one listener and records the mapping. Also fixes the pre-existing unit-test Makefile link gap: four tests that include `mysqlx_thread.cpp` (which references `MysqlxConfigStore::resolve_identity`) now correctly link `mysqlx_config_store.cpp`, and the robustness test link line picks up `mysqlx_config_store.cpp` + `mysqlx_listener_reconcile.cpp` for the new coverage. Out of scope: switching the distribution strategy to SO_REUSEPORT / all-threads-on-all-routes (future design iteration); treating an in-place `active` flag toggle as anything other than remove + add. |
2 months ago |
|
|
3e8c3da9de |
fix(mysqlx): preserve backend TLS state past auth handshake
What: Stop rewrapping the raw backend fd in a fresh session-owned MysqlxDataStream after backend auth completes. Route every data-plane read/write (forward_to_backend, handler_waiting_server_msg, handler_session_reset_waiting, and the cached-conn attach path) through MysqlxConnection::backend_ds() instead. Why: The optional backend TLS handshake runs against the connection's backend_ds_ (see mysqlx_connection.cpp init_ssl_connect) and the resulting SSL* lives there. The prior session code discarded that SSL* by init()-ing a new plain MysqlxDataStream around the same raw fd, then used it for cleartext I/O on a socket where the server expects TLS frames. Flagged in the ProtocolX code review (item #2). Testing: mysqlx_robustness_unit-t picks up five new assertions that pin the invariant: MysqlxSession::server_ds() aliases backend_conn_->backend_ds() whenever a backend is attached, and falls back to a fd == -1 placeholder otherwise. Baseline 33, post-fix 38, all pass. Out of scope: - Connection cache semantics: cached connections keep their SSL state; MysqlxConnection::reset() already leaves backend_ds_ untouched so the invariant extends to pooled reuse. - MysqlxDataStream internals (untouched). - The dormant worker path (different PR). Pre-existing ripple: test/tap/tests/unit/Makefile mysqlx_robustness_unit-t link line was missing mysqlx_config_store.cpp, same gap other mysqlx tests have been closing as they get touched. Added it here so the test links. |
2 months ago |
|
|
98aee7db21 |
chore(mysqlx): retire dormant MysqlxWorker path and its smoke test
Deletes the mysqlx worker implementation and the TAP smoke test that was its only caller. Also removes the corresponding entries from the plugin Makefile, the unit-tests Makefile, and the CI groups manifest. Files removed: - plugins/mysqlx/src/mysqlx_worker.cpp - plugins/mysqlx/include/mysqlx_worker.h - test/tap/tests/test_mysqlx_listener_smoke-t.cpp References cleaned: - plugins/mysqlx/Makefile: dropped mysqlx_worker.cpp from SRCS - test/tap/tests/unit/Makefile: dropped test_mysqlx_listener_smoke-t from UNIT_TESTS and deleted its build rule - test/tap/groups/groups.json: removed the smoke-test entry from the unit-tests-g1 group Why: the worker was an earlier parallel implementation of the mysqlx session path (separate accept thread + worker queue + per-listener route tracking + identity.default_route -> pick_endpoint). It never reached any call site in the production proxysql binary. The only consumers of its public API were the smoke test and the worker code itself. The route-identity fix in PR #5641 used the worker's design as a reference and implemented the fix in the active session path (Mysqlx_Thread + MysqlxSession + MysqlxConfigStore::pick_endpoint). With that landed, the dormant code is pure tech debt — reviewers repeatedly mistook its logic for the active path. Why retire the smoke test at the same time: the test exercised mysqlx_start_listeners_from_runtime_routes() which was the worker's only entry function. With the worker gone, the smoke test cannot link; keeping it would produce a broken CI target. Out of scope: no changes to the active session path, no changes to the MysqlxConfigStore or admin-schema tables, no changes to any other tests. Pure deletion + Makefile/groups cleanup. |
2 months ago |
|
|
e099081796 |
fix(mysqlx): record stats on unreachable guard, update stale comment
What:
- resolve_backend_target()'s !identity_ guard now calls
mysqlx_stats().record_conn_err("", 0) so the missing-identity path does
not silently drop observability.
- Stale "Not yet wired" comment in mysqlx_session.h replaced with a
current-state doc reflecting that resolve_backend_target() is called
from the auth handlers before send_auth_ok().
Why:
- Final review flagged that the spec's "all failure modes record stats"
contract wasn't held on the programming-error guard. Tuple ("", 0)
matches the empty-route 4000 case, which is acceptable aliasing since
both legitimately lack route context.
- Comment update prevents future confusion about whether resolve is
integrated (Task 4 is merged).
Out of scope: no behavior change, no test change. Assertion count
remains 46.
|
2 months ago |
|
|
14c5d68260 |
feat(mysqlx): add resolve_backend_target() method on session
Introduces MysqlxSession::resolve_backend_target(), a private method that translates the authenticated user's identity_->default_route into the concrete (target_hostgroup_, target_address_, target_port_) triple that handler_connecting_server needs to reach the backend. The method reads identity_->default_route, looks up the hostgroup + endpoint via the thread's MysqlxConfigStore, and returns 0 on success; on failure it returns 4000/4001/4002 (empty default_route / unknown route / no-backend respectively), emits an X-Protocol Error frame, records a stats miss via mysqlx_stats().record_conn_err(), and marks the session unhealthy. Why: fixes the design gap where sessions populated target_* fields only from a pool cache lookup and otherwise connected to "" on port 0 (see docs/superpowers/specs/2026-04-17-mysqlx-route-identity-design.md). The pre-Ok timing is deliberate — once the X-Protocol Ok frame is on the wire, a routing failure cannot be cleanly reported, so resolution must run before send_auth_ok(). Invariants preserved: resolve_backend_target() is not yet wired into the auth flow. Task 4 owns that step. This commit is therefore a pure addition — no production call site touches the new method; only the new unit tests exercise it, via test-only public accessors (inject_identity_for_test / resolve_backend_target_for_test / target_*_for_test). Mysqlx_Thread gains get_config_store() so the session can reach the store; MysqlxStatsStore gains reset_for_test() and get_last_conn_err_for_test() so the stats-recording contract can be asserted without a live SQLite stats DB. Out of scope: wiring resolve_backend_target() into handle_auth_plain and handler_auth_challenge_response (Task 4); removing the dormant worker path (Task 5); reconciling error codes 4000/4001/4002 with the project-wide ProxySQL error-code policy (follow-up commit). Test coverage (plan bumped +7, now 43 assertions): - test_routing_happy_path — rc=0, target fields populated - test_routing_no_default_route — rc=4000 - test_routing_unknown_route — rc=4001 - test_routing_no_backend — rc=4002 (route exists, 0 endpoints) - test_routing_stats_on_failure — (route,hg) tuples for all 3 modes - test_routing_unknown_user — identity_lookup nullopt => unhealthy Also updates mysqlx_session_unit-t / mysqlx_thread_unit-t / mysqlx_message_dispatch_unit-t / mysqlx_concurrent_unit-t Makefile rules to link mysqlx_config_store.cpp + mysqlx_stats.cpp, which were already required transitively by mysqlx_thread.cpp::resolve_identity() since Task 2 but had not been wired into the unit-test link set. |
2 months ago |
|
|
f0a8655be3 |
refactor(mysqlx): replace MysqlxCredentials with MysqlxResolvedIdentity
The session's identity-lookup callback now returns the full MysqlxResolvedIdentity struct from the config store rather than a stripped-down MysqlxCredentials. Password-hash derivation moves into the session via a private derive_stored_hash helper (handles both "*HEX40" and cleartext forms). No behavior change; the existing credential-lookup guard semantics are preserved so sessions without a lookup still follow the open-proxy path exercised by the robustness tests. mysqlx_robustness_unit-t passes unchanged (36/36). |
2 months ago |
|
|
ca6ddb860c |
feat(mysqlx): add route_exists() predicate to MysqlxConfigStore
Needed to distinguish unknown-route from route-with-no-backend when resolving a session's backend target. route_hostgroup() returns 0 in both cases and can't be used for disambiguation. |
2 months ago |
|
|
4b353ba0b1 |
test(mysqlx): add MysqlxConfigStore::install_for_test helper
Adds a test-only seam on MysqlxConfigStore so unit tests can populate the store's private routes_ and hostgroup_endpoints_ maps directly, without building a runtime SQLite3DB fixture. This unblocks the upcoming route-identity unit tests that need to exercise route lookups in isolation. Also wires plugins/mysqlx/src/mysqlx_config_store.cpp into the mysqlx_robustness_unit-t link line. The file is already listed in the plugin's own Makefile but was missing from the unit-test target, so since MysqlxConfigStore::resolve_identity became a runtime dependency of Mysqlx_Thread the link had been broken. |
2 months ago |
|
|
923cbfeadc |
fix(mysqlx): resolve critical authentication, TLS, and data integrity bugs
- Wire credential_lookup to sessions via config store so frontend auth verification actually runs (was silently bypassed for all users) - Use backend_password (cleartext) instead of password_hash (double-SHA1) for backend MYSQL41 scramble computation - Replace cumulative BIO counter comparison with BIO_ctrl_pending() to fix permanent POLLOUT busy-loop with TLS connections - Add poll(POLLOUT) before getsockopt(SO_ERROR) in check_connect() to correctly detect non-blocking connect completion on Linux - Wrap DELETE+INSERT with BEGIN/COMMIT in sync_disk_to_memory and copy_to_runtime to prevent data loss on crash |
2 months ago |
|
|
04f09d6535 |
fix(mysqlx): address SonarCloud quality gate failures
- Replace void* thread_ptr_ with typed Mysqlx_Thread* for type safety - Use RAII (unique_ptr) for SQLite3 result and error cleanup in plugin - Extract handle_auth_mysql41/handle_auth_plain from handler_auth_start to reduce cognitive complexity (37 -> ~10 per method) - Extract step_auth sub-states into individual methods to reduce cognitive complexity in mysqlx_connection.cpp (55 -> ~8 per method) - Add forward_frame_to_client helper to eliminate repeated frame forwarding code - Replace hardcoded password strings in credential verify test with named test constants - Add NOSONAR annotations for const_cast (required by SQLite3DB API) and SSL_VERIFY_NONE (test-only self-signed certs) - Fix Makefile whitespace issue with mysqlx unit test entries |
2 months ago |
|
|
04c0303ee9 |
fix(mysqlx): address critical code review feedback from PR #5593
- PluginManager: don't mark plugin stopped when stop() fails - PluginManager: add mutex to proxysql_get_plugin_manager() access - PluginManager: reject duplicate plugin paths in load() - mysqlx_thread: add listener_mutex_ for listener_fds_ synchronization - mysqlx_config_store: return tls_mode by value, not by reference - Makefile: propagate PROXYSQLGENAI/31/FFTO/TSDB flags to mysqlx builds |
2 months ago |
|
|
30dc111b1d |
feat(mysqlx): add TLS passthrough mode for raw TLS forwarding
Adds MysqlxTlsMode enum and passthrough mode support: - TLS_OFF: No TLS (default, existing behavior) - TLS_TERMINATE: ProxySQL terminates TLS, decrypts, re-encrypts to backend (existing behavior when TLS is negotiated) - TLS_PASSTHROUGH: Raw encrypted bytes forwarded between client and backend without TLS termination Passthrough behavior: - handler_tls_accept_init(): skips SSL init and handshake entirely, returns to CONNECTING_CLIENT state immediately - handler_connecting_server(): skips backend TLS setup when in passthrough mode (backend_tls_required_ is not set) - X Protocol frames are still parsed at the framing layer since the proxy needs to route and multiplex connections Use cases for passthrough mode: - TLS terminated at an external load balancer - ProxySQL should not have access to TLS certificates - Compliance requirements that forbid TLS inspection set_tls_mode() / get_tls_mode() accessors on MysqlxSession allow runtime configuration of the TLS mode per session. |
2 months ago |