Full Code of fatedier/frp for AI

dev 38a71a6803ee cached
445 files
1.6 MB
479.0k tokens
2183 symbols
3 requests
Download .txt
Showing preview only (1,770K chars total). Download the full file or copy to clipboard to get everything.
Repository: fatedier/frp
Branch: dev
Commit: 38a71a6803ee
Files: 445
Total size: 1.6 MB

Directory structure:
gitextract_d931dhl1/

├── .circleci/
│   └── config.yml
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   ├── config.yml
│   │   └── feature_request.yaml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── build-and-push-image.yml
│       ├── golangci-lint.yml
│       ├── goreleaser.yml
│       └── stale.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── AGENTS.md
├── LICENSE
├── Makefile
├── Makefile.cross-compiles
├── README.md
├── README_zh.md
├── Release.md
├── assets/
│   └── assets.go
├── client/
│   ├── api_router.go
│   ├── config_manager.go
│   ├── config_manager_test.go
│   ├── configmgmt/
│   │   └── types.go
│   ├── connector.go
│   ├── control.go
│   ├── event/
│   │   └── event.go
│   ├── health/
│   │   └── health.go
│   ├── http/
│   │   ├── controller.go
│   │   ├── controller_test.go
│   │   └── model/
│   │       ├── proxy_definition.go
│   │       ├── types.go
│   │       └── visitor_definition.go
│   ├── proxy/
│   │   ├── general_tcp.go
│   │   ├── proxy.go
│   │   ├── proxy_manager.go
│   │   ├── proxy_wrapper.go
│   │   ├── sudp.go
│   │   ├── udp.go
│   │   └── xtcp.go
│   ├── service.go
│   ├── service_test.go
│   └── visitor/
│       ├── stcp.go
│       ├── sudp.go
│       ├── visitor.go
│       ├── visitor_manager.go
│       └── xtcp.go
├── cmd/
│   ├── frpc/
│   │   ├── main.go
│   │   └── sub/
│   │       ├── admin.go
│   │       ├── nathole.go
│   │       ├── proxy.go
│   │       ├── root.go
│   │       └── verify.go
│   └── frps/
│       ├── main.go
│       ├── root.go
│       └── verify.go
├── conf/
│   ├── frpc.toml
│   ├── frpc_full_example.toml
│   ├── frps.toml
│   ├── frps_full_example.toml
│   └── legacy/
│       ├── frpc_legacy_full.ini
│       └── frps_legacy_full.ini
├── doc/
│   ├── server_plugin.md
│   ├── ssh_tunnel_gateway.md
│   └── virtual_net.md
├── dockerfiles/
│   ├── Dockerfile-for-frpc
│   └── Dockerfile-for-frps
├── go.mod
├── go.sum
├── hack/
│   ├── download.sh
│   └── run-e2e.sh
├── package.sh
├── pkg/
│   ├── auth/
│   │   ├── auth.go
│   │   ├── legacy/
│   │   │   └── legacy.go
│   │   ├── oidc.go
│   │   ├── oidc_test.go
│   │   ├── pass.go
│   │   └── token.go
│   ├── config/
│   │   ├── flags.go
│   │   ├── legacy/
│   │   │   ├── README.md
│   │   │   ├── client.go
│   │   │   ├── conversion.go
│   │   │   ├── parse.go
│   │   │   ├── proxy.go
│   │   │   ├── server.go
│   │   │   ├── utils.go
│   │   │   ├── value.go
│   │   │   └── visitor.go
│   │   ├── load.go
│   │   ├── load_test.go
│   │   ├── source/
│   │   │   ├── aggregator.go
│   │   │   ├── aggregator_test.go
│   │   │   ├── base_source.go
│   │   │   ├── base_source_test.go
│   │   │   ├── clone.go
│   │   │   ├── config_source.go
│   │   │   ├── config_source_test.go
│   │   │   ├── source.go
│   │   │   ├── store.go
│   │   │   └── store_test.go
│   │   ├── template.go
│   │   ├── types/
│   │   │   ├── types.go
│   │   │   └── types_test.go
│   │   └── v1/
│   │       ├── api.go
│   │       ├── client.go
│   │       ├── client_test.go
│   │       ├── clone_test.go
│   │       ├── common.go
│   │       ├── decode.go
│   │       ├── decode_test.go
│   │       ├── proxy.go
│   │       ├── proxy_plugin.go
│   │       ├── proxy_test.go
│   │       ├── server.go
│   │       ├── server_test.go
│   │       ├── store.go
│   │       ├── validation/
│   │       │   ├── client.go
│   │       │   ├── common.go
│   │       │   ├── oidc.go
│   │       │   ├── oidc_test.go
│   │       │   ├── plugin.go
│   │       │   ├── proxy.go
│   │       │   ├── server.go
│   │       │   ├── validation.go
│   │       │   ├── validator.go
│   │       │   └── visitor.go
│   │       ├── value_source.go
│   │       ├── value_source_test.go
│   │       ├── visitor.go
│   │       └── visitor_plugin.go
│   ├── errors/
│   │   └── errors.go
│   ├── metrics/
│   │   ├── aggregate/
│   │   │   └── server.go
│   │   ├── mem/
│   │   │   ├── server.go
│   │   │   └── types.go
│   │   ├── metrics.go
│   │   └── prometheus/
│   │       └── server.go
│   ├── msg/
│   │   ├── ctl.go
│   │   ├── handler.go
│   │   └── msg.go
│   ├── naming/
│   │   ├── names.go
│   │   └── names_test.go
│   ├── nathole/
│   │   ├── analysis.go
│   │   ├── classify.go
│   │   ├── controller.go
│   │   ├── discovery.go
│   │   ├── nathole.go
│   │   └── utils.go
│   ├── plugin/
│   │   ├── client/
│   │   │   ├── http2http.go
│   │   │   ├── http2https.go
│   │   │   ├── http_proxy.go
│   │   │   ├── https2http.go
│   │   │   ├── https2https.go
│   │   │   ├── plugin.go
│   │   │   ├── socks5.go
│   │   │   ├── static_file.go
│   │   │   ├── tls2raw.go
│   │   │   ├── unix_domain_socket.go
│   │   │   └── virtual_net.go
│   │   ├── server/
│   │   │   ├── http.go
│   │   │   ├── manager.go
│   │   │   ├── plugin.go
│   │   │   ├── tracer.go
│   │   │   └── types.go
│   │   └── visitor/
│   │       ├── plugin.go
│   │       └── virtual_net.go
│   ├── policy/
│   │   ├── featuregate/
│   │   │   └── feature_gate.go
│   │   └── security/
│   │       └── unsafe.go
│   ├── proto/
│   │   └── udp/
│   │       ├── udp.go
│   │       └── udp_test.go
│   ├── sdk/
│   │   └── client/
│   │       └── client.go
│   ├── ssh/
│   │   ├── gateway.go
│   │   ├── server.go
│   │   └── terminal.go
│   ├── transport/
│   │   ├── message.go
│   │   └── tls.go
│   ├── util/
│   │   ├── http/
│   │   │   ├── context.go
│   │   │   ├── error.go
│   │   │   ├── handler.go
│   │   │   ├── http.go
│   │   │   ├── middleware.go
│   │   │   └── server.go
│   │   ├── jsonx/
│   │   │   ├── json_v1.go
│   │   │   └── raw_message.go
│   │   ├── limit/
│   │   │   ├── reader.go
│   │   │   └── writer.go
│   │   ├── log/
│   │   │   └── log.go
│   │   ├── metric/
│   │   │   ├── counter.go
│   │   │   ├── counter_test.go
│   │   │   ├── date_counter.go
│   │   │   ├── date_counter_test.go
│   │   │   └── metrics.go
│   │   ├── net/
│   │   │   ├── conn.go
│   │   │   ├── dial.go
│   │   │   ├── dns.go
│   │   │   ├── http.go
│   │   │   ├── kcp.go
│   │   │   ├── listener.go
│   │   │   ├── proxyprotocol.go
│   │   │   ├── proxyprotocol_test.go
│   │   │   ├── tls.go
│   │   │   ├── udp.go
│   │   │   └── websocket.go
│   │   ├── system/
│   │   │   ├── system.go
│   │   │   └── system_android.go
│   │   ├── tcpmux/
│   │   │   └── httpconnect.go
│   │   ├── util/
│   │   │   ├── types.go
│   │   │   ├── util.go
│   │   │   └── util_test.go
│   │   ├── version/
│   │   │   └── version.go
│   │   ├── vhost/
│   │   │   ├── http.go
│   │   │   ├── https.go
│   │   │   ├── https_test.go
│   │   │   ├── resource.go
│   │   │   ├── router.go
│   │   │   └── vhost.go
│   │   ├── wait/
│   │   │   └── backoff.go
│   │   └── xlog/
│   │       ├── ctx.go
│   │       ├── log_writer.go
│   │       └── xlog.go
│   ├── virtual/
│   │   └── client.go
│   └── vnet/
│       ├── controller.go
│       ├── message.go
│       ├── tun.go
│       ├── tun_darwin.go
│       ├── tun_linux.go
│       └── tun_unsupported.go
├── server/
│   ├── api_router.go
│   ├── control.go
│   ├── controller/
│   │   └── resource.go
│   ├── group/
│   │   ├── base.go
│   │   ├── base_test.go
│   │   ├── group.go
│   │   ├── http.go
│   │   ├── https.go
│   │   ├── listener.go
│   │   ├── listener_test.go
│   │   ├── registry.go
│   │   ├── registry_test.go
│   │   ├── tcp.go
│   │   └── tcpmux.go
│   ├── http/
│   │   ├── controller.go
│   │   ├── controller_test.go
│   │   └── model/
│   │       └── types.go
│   ├── metrics/
│   │   └── metrics.go
│   ├── ports/
│   │   └── ports.go
│   ├── proxy/
│   │   ├── http.go
│   │   ├── https.go
│   │   ├── proxy.go
│   │   ├── stcp.go
│   │   ├── sudp.go
│   │   ├── tcp.go
│   │   ├── tcpmux.go
│   │   ├── udp.go
│   │   └── xtcp.go
│   ├── registry/
│   │   └── registry.go
│   ├── service.go
│   └── visitor/
│       └── visitor.go
├── test/
│   └── e2e/
│       ├── e2e.go
│       ├── e2e_test.go
│       ├── examples.go
│       ├── framework/
│       │   ├── cleanup.go
│       │   ├── client.go
│       │   ├── consts/
│       │   │   └── consts.go
│       │   ├── expect.go
│       │   ├── framework.go
│       │   ├── log.go
│       │   ├── mockservers.go
│       │   ├── process.go
│       │   ├── request.go
│       │   ├── test_context.go
│       │   └── util.go
│       ├── legacy/
│       │   ├── basic/
│       │   │   ├── basic.go
│       │   │   ├── client.go
│       │   │   ├── client_server.go
│       │   │   ├── cmd.go
│       │   │   ├── config.go
│       │   │   ├── http.go
│       │   │   ├── server.go
│       │   │   ├── tcpmux.go
│       │   │   └── xtcp.go
│       │   ├── features/
│       │   │   ├── bandwidth_limit.go
│       │   │   ├── chaos.go
│       │   │   ├── group.go
│       │   │   ├── heartbeat.go
│       │   │   ├── monitor.go
│       │   │   └── real_ip.go
│       │   └── plugin/
│       │       ├── client.go
│       │       └── server.go
│       ├── mock/
│       │   └── server/
│       │       ├── httpserver/
│       │       │   └── server.go
│       │       ├── interface.go
│       │       ├── oidcserver/
│       │       │   └── oidcserver.go
│       │       └── streamserver/
│       │           └── server.go
│       ├── pkg/
│       │   ├── cert/
│       │   │   ├── generator.go
│       │   │   └── selfsigned.go
│       │   ├── plugin/
│       │   │   └── plugin.go
│       │   ├── port/
│       │   │   ├── port.go
│       │   │   └── util.go
│       │   ├── process/
│       │   │   └── process.go
│       │   ├── request/
│       │   │   └── request.go
│       │   ├── rpc/
│       │   │   └── rpc.go
│       │   └── ssh/
│       │       └── client.go
│       ├── suites.go
│       └── v1/
│           ├── basic/
│           │   ├── annotations.go
│           │   ├── basic.go
│           │   ├── client.go
│           │   ├── client_server.go
│           │   ├── cmd.go
│           │   ├── config.go
│           │   ├── http.go
│           │   ├── oidc.go
│           │   ├── server.go
│           │   ├── tcpmux.go
│           │   ├── token_source.go
│           │   └── xtcp.go
│           ├── features/
│           │   ├── bandwidth_limit.go
│           │   ├── chaos.go
│           │   ├── group.go
│           │   ├── heartbeat.go
│           │   ├── monitor.go
│           │   ├── real_ip.go
│           │   ├── ssh_tunnel.go
│           │   └── store.go
│           └── plugin/
│               ├── client.go
│               └── server.go
└── web/
    ├── frpc/
    │   ├── .gitignore
    │   ├── .prettierrc.json
    │   ├── Makefile
    │   ├── README.md
    │   ├── auto-imports.d.ts
    │   ├── components.d.ts
    │   ├── embed.go
    │   ├── embed_stub.go
    │   ├── env.d.ts
    │   ├── eslint.config.js
    │   ├── index.html
    │   ├── package.json
    │   ├── src/
    │   │   ├── App.vue
    │   │   ├── api/
    │   │   │   ├── frpc.ts
    │   │   │   └── http.ts
    │   │   ├── assets/
    │   │   │   └── css/
    │   │   │       ├── _form-layout.scss
    │   │   │       ├── _index.scss
    │   │   │       ├── _mixins.scss
    │   │   │       ├── _variables.scss
    │   │   │       ├── dark.css
    │   │   │       └── var.css
    │   │   ├── components/
    │   │   │   ├── ConfigField.vue
    │   │   │   ├── ConfigSection.vue
    │   │   │   ├── KeyValueEditor.vue
    │   │   │   ├── ProxyCard.vue
    │   │   │   ├── StatusPills.vue
    │   │   │   ├── StringListEditor.vue
    │   │   │   ├── proxy-form/
    │   │   │   │   ├── ProxyAuthSection.vue
    │   │   │   │   ├── ProxyBackendSection.vue
    │   │   │   │   ├── ProxyBaseSection.vue
    │   │   │   │   ├── ProxyFormLayout.vue
    │   │   │   │   ├── ProxyHealthSection.vue
    │   │   │   │   ├── ProxyHttpSection.vue
    │   │   │   │   ├── ProxyLoadBalanceSection.vue
    │   │   │   │   ├── ProxyMetadataSection.vue
    │   │   │   │   ├── ProxyNatSection.vue
    │   │   │   │   ├── ProxyRemoteSection.vue
    │   │   │   │   └── ProxyTransportSection.vue
    │   │   │   └── visitor-form/
    │   │   │       ├── VisitorBaseSection.vue
    │   │   │       ├── VisitorConnectionSection.vue
    │   │   │       ├── VisitorFormLayout.vue
    │   │   │       ├── VisitorTransportSection.vue
    │   │   │       └── VisitorXtcpSection.vue
    │   │   ├── composables/
    │   │   │   └── useResponsive.ts
    │   │   ├── main.ts
    │   │   ├── router/
    │   │   │   └── index.ts
    │   │   ├── stores/
    │   │   │   ├── client.ts
    │   │   │   ├── proxy.ts
    │   │   │   └── visitor.ts
    │   │   ├── svg.d.ts
    │   │   ├── types/
    │   │   │   ├── constants.ts
    │   │   │   ├── index.ts
    │   │   │   ├── proxy-converters.ts
    │   │   │   ├── proxy-form.ts
    │   │   │   ├── proxy-status.ts
    │   │   │   └── proxy-store.ts
    │   │   ├── utils/
    │   │   │   └── format.ts
    │   │   └── views/
    │   │       ├── ClientConfigure.vue
    │   │       ├── ProxyDetail.vue
    │   │       ├── ProxyEdit.vue
    │   │       ├── ProxyList.vue
    │   │       ├── VisitorDetail.vue
    │   │       ├── VisitorEdit.vue
    │   │       └── VisitorList.vue
    │   ├── tsconfig.json
    │   ├── tsconfig.node.json
    │   └── vite.config.mts
    ├── frps/
    │   ├── .gitignore
    │   ├── .prettierrc.json
    │   ├── Makefile
    │   ├── README.md
    │   ├── auto-imports.d.ts
    │   ├── components.d.ts
    │   ├── embed.go
    │   ├── embed_stub.go
    │   ├── env.d.ts
    │   ├── eslint.config.js
    │   ├── index.html
    │   ├── package.json
    │   ├── src/
    │   │   ├── App.vue
    │   │   ├── api/
    │   │   │   ├── client.ts
    │   │   │   ├── http.ts
    │   │   │   ├── proxy.ts
    │   │   │   └── server.ts
    │   │   ├── assets/
    │   │   │   └── css/
    │   │   │       ├── custom.css
    │   │   │       ├── dark.css
    │   │   │       └── var.css
    │   │   ├── components/
    │   │   │   ├── ClientCard.vue
    │   │   │   ├── ProxyCard.vue
    │   │   │   ├── StatCard.vue
    │   │   │   └── Traffic.vue
    │   │   ├── composables/
    │   │   │   └── useResponsive.ts
    │   │   ├── main.ts
    │   │   ├── router/
    │   │   │   └── index.ts
    │   │   ├── svg.d.ts
    │   │   ├── types/
    │   │   │   ├── client.ts
    │   │   │   ├── proxy.ts
    │   │   │   └── server.ts
    │   │   ├── utils/
    │   │   │   ├── client.ts
    │   │   │   ├── format.ts
    │   │   │   └── proxy.ts
    │   │   └── views/
    │   │       ├── ClientDetail.vue
    │   │       ├── Clients.vue
    │   │       ├── Proxies.vue
    │   │       ├── ProxyDetail.vue
    │   │       └── ServerOverview.vue
    │   ├── tsconfig.json
    │   ├── tsconfig.node.json
    │   └── vite.config.mts
    ├── package.json
    └── shared/
        ├── components/
        │   ├── ActionButton.vue
        │   ├── BaseDialog.vue
        │   ├── ConfirmDialog.vue
        │   ├── FilterDropdown.vue
        │   ├── PopoverMenu.vue
        │   └── PopoverMenuItem.vue
        ├── css/
        │   ├── _index.scss
        │   ├── _mixins.scss
        │   └── _variables.scss
        └── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
  go-version-latest:
    docker:
    - image: cimg/go:1.25-node
    resource_class: large
    steps:
    - checkout
    - run:
        name: Build web assets (frps)
        command: make install build
        working_directory: web/frps
    - run:
        name: Build web assets (frpc)
        command: make install build
        working_directory: web/frpc
    - run: make
    - run: make alltest

workflows:
  version: 2
  build_and_test:
    jobs:
    - go-version-latest


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [fatedier]
custom: ["https://afdian.com/a/fatedier"]


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: Bug report
description: Report a bug to help us improve frp

body:
- type: markdown
  attributes:
    value: |
      Thanks for taking the time to fill out this bug report!
- type: textarea
  id: bug-description
  attributes:
    label: Bug Description
    description: Tell us what issues you ran into
    placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
  validations:
    required: true
- type: input
  id: frpc-version
  attributes:
    label: frpc Version
    description: Include the output of `frpc -v`
  validations:
    required: true
- type: input
  id: frps-version
  attributes:
    label: frps Version
    description: Include the output of `frps -v`
  validations:
    required: true
- type: input
  id: system-architecture
  attributes:
    label: System Architecture
    description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
  validations:
    required: true
- type: textarea
  id: config
  attributes:
    label: Configurations
    description: Include what configurrations you used and ran into this problem
    placeholder: Pay attention to hiding the token and password in your output
  validations:
    required: true
- type: textarea
  id: log
  attributes:
    label: Logs
    description: Prefer you providing releated error logs here
    placeholder: Pay attention to hiding your personal informations
- type: textarea
  id: steps-to-reproduce
  attributes:
    label: Steps to reproduce
    description: How to reproduce it? It's important for us to find the bug
    value: |
      1. 
      2. 
      3. 
      ...
- type: checkboxes
  id: area
  attributes:
    label: Affected area
    options:
    - label: "Docs"
    - label: "Installation"
    - label: "Performance and Scalability"
    - label: "Security"
    - label: "User Experience"
    - label: "Test and Release"
    - label: "Developer Infrastructure"
    - label: "Client Plugin"
    - label: "Server Plugin"
    - label: "Extensions"
    - label: "Others"


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yaml
================================================
name: Feature Request
description: Suggest an idea to improve frp
title: "[Feature Request] "

body:
- type: markdown
  attributes:
    value: |
      This is only used to request new product features.
- type: textarea
  id: feature-request
  attributes:
    label: Describe the feature request
    description: Tell us what's you want and why it should be added in frp.
  validations:
    required: true
- type: textarea
  id: alternatives
  attributes:
    label: Describe alternatives you've considered
- type: checkboxes
  id: area
  attributes:
    label: Affected area
    options:
    - label: "Docs"
    - label: "Installation"
    - label: "Performance and Scalability"
    - label: "Security"
    - label: "User Experience"
    - label: "Test and Release"
    - label: "Developer Infrastructure"
    - label: "Client Plugin"
    - label: "Server Plugin"
    - label: "Extensions"
    - label: "Others"


================================================
FILE: .github/pull_request_template.md
================================================
### WHY

<!-- author to complete -->


================================================
FILE: .github/workflows/build-and-push-image.yml
================================================
name: Build Image and Publish to Dockerhub & GPR

on:
  release:
    types: [ published ]
  workflow_dispatch:
    inputs:
      tag:
        description: 'Image tag'
        required: true
        default: 'test'
permissions:
  contents: read

jobs:
  image:
    name: Build Image from Dockerfile and binaries
    runs-on: ubuntu-latest
    steps:
      # environment
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: '0'

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # get image tag name
      - name: Get Image Tag Name
        run: |
          if [ x${{ github.event.inputs.tag }} == x"" ]; then
            echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
          else
            echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
          fi
      - name: Login to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: Login to the GPR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GPR_TOKEN }}

      # prepare image tags
      - name: Prepare Image Tags
        run: |
          echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV
          echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV
          echo "TAG_FRPC=fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
          echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
          echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
          echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV

      - name: Build and push frpc
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./dockerfiles/Dockerfile-for-frpc
          platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
          push: true
          tags: |
            ${{ env.TAG_FRPC }}
            ${{ env.TAG_FRPC_GPR }}

      - name: Build and push frps
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./dockerfiles/Dockerfile-for-frps
          platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
          push: true
          tags: |
            ${{ env.TAG_FRPS }}
            ${{ env.TAG_FRPS_GPR }}


================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint
on:
  push:
    branches:
    - master
    - dev
  pull_request:
permissions:
  contents: read
  # Optional: allow read access to pull request. Use with `only-new-issues` option.
  pull-requests: read
jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-go@v5
      with:
        go-version: '1.25'
        cache: false
    - uses: actions/setup-node@v4
      with:
        node-version: '22'
    - name: Build web assets (frps)
      run: make build
      working-directory: web/frps
    - name: Build web assets (frpc)
      run: make build
      working-directory: web/frpc
    - name: golangci-lint
      uses: golangci/golangci-lint-action@v9
      with:
        # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
        version: v2.10


================================================
FILE: .github/workflows/goreleaser.yml
================================================
name: goreleaser

on:
  workflow_dispatch:

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.25'
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - name: Build web assets (frps)
        run: make build
        working-directory: web/frps
      - name: Build web assets (frpc)
        run: make build
        working-directory: web/frpc
      - name: Make All
        run: |
          ./package.sh

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          version: latest
          args: release --clean --release-notes=./Release.md
        env:
          GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}


================================================
FILE: .github/workflows/stale.yml
================================================
name: "Close stale issues and PRs"
on:
  schedule:
  - cron: "20 0 * * *"
  workflow_dispatch:
    inputs:
      debug-only:
        description: 'In debug mod'
        required: false
        default: 'false'
permissions:
  contents: read

jobs:
  stale:
    permissions:
      issues: write  # for actions/stale to close stale issues
      pull-requests: write  # for actions/stale to close stale PRs
      actions: write
    runs-on: ubuntu-latest
    steps:
    - uses: actions/stale@v9
      with:
        stale-issue-message: 'Issues go stale after 14d of inactivity. Stale issues rot after an additional 3d of inactivity and eventually close.'
        stale-pr-message: "PRs go stale after 14d of inactivity. Stale PRs rot after an additional 3d of inactivity and eventually close."
        stale-issue-label: 'lifecycle/stale'
        exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
        stale-pr-label: 'lifecycle/stale'
        exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
        days-before-stale: 14
        days-before-close: 3
        debug-only: ${{ github.event.inputs.debug-only }}
        exempt-all-pr-milestones: true
        exempt-all-pr-assignees: true
        operations-per-run: 200


================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

*.exe
*.test
*.prof

# Self
bin/
packages/
release/
test/bin/
vendor/
lastversion/
dist/
.idea/
.vscode/
.autogen_ssh_key
client.crt
client.key

node_modules/

# Cache
*.swp

# AI
.claude/
.sisyphus/
.superpowers/


================================================
FILE: .golangci.yml
================================================
version: "2"
run:
  concurrency: 4
  timeout: 20m
  build-tags:
  - integ
  - integfuzz
linters:
  default: none
  enable:
  - asciicheck
  - copyloopvar
  - errcheck
  - gocritic
  - gosec
  - govet
  - ineffassign
  - lll
  - makezero
  - misspell
  - modernize
  - prealloc
  - predeclared
  - revive
  - staticcheck
  - unconvert
  - unparam
  - unused
  settings:
    errcheck:
      check-type-assertions: false
      check-blank: false
    gocritic:
      disabled-checks:
      - exitAfterDefer
    gosec:
      excludes: ["G115", "G117", "G204", "G401", "G402", "G404", "G501", "G703", "G704", "G705"]
      severity: low
      confidence: low
    govet:
      disable:
      - shadow
    lll:
      line-length: 160
      tab-width: 1
    misspell:
      locale: US
      ignore-rules:
      - cancelled
      - marshalled
    modernize:
      disable:
      - omitzero
    unparam:
      check-exported: false
  exclusions:
    generated: lax
    presets:
    - comments
    - common-false-positives
    - legacy
    - std-error-handling
    rules:
    - linters:
      - errcheck
      - maligned
      path: _test\.go$|^tests/|^samples/
    - linters:
      - revive
      - staticcheck
      text: use underscores in Go names
    - linters:
      - revive
      text: unused-parameter
    - linters:
      - revive
      text: "avoid meaningless package names"
    - linters:
      - revive
      text: "Go standard library package names"
    - linters:
      - unparam
      text: is always false
    paths:
    - .*\.pb\.go
    - .*\.gen\.go
    - genfiles$
    - vendor$
    - bin$
    - third_party$
    - builtin$
    - examples$
    - node_modules
formatters:
  enable:
  - gci
  - gofumpt
  - goimports
  settings:
    gci:
      sections:
      - standard
      - default
      - prefix(github.com/fatedier/frp/)
  exclusions:
    generated: lax
    paths:
    - .*\.pb\.go
    - .*\.gen\.go
    - genfiles$
    - vendor$
    - bin$
    - third_party$
    - builtin$
    - examples$
    - node_modules
issues:
  max-issues-per-linter: 0
  max-same-issues: 0


================================================
FILE: .goreleaser.yml
================================================
builds:
  - skip: true
checksum:
  name_template: '{{ .ProjectName }}_sha256_checksums.txt'
  algorithm: sha256
  extra_files:
  - glob: ./release/packages/*
release:
  # Same as for github
  # Note: it can only be one: either github, gitlab or gitea
  github:
    owner: fatedier
    name: frp

  draft: false

  # You can add extra pre-existing files to the release.
  # The filename on the release will be the last part of the path (base). If
  # another file with the same name exists, the latest one found will be used.
  # Defaults to empty.
  extra_files:
    - glob: ./release/packages/*


================================================
FILE: AGENTS.md
================================================
# AGENTS.md

## Development Commands

### Build
- `make build` - Build both frps and frpc binaries
- `make frps` - Build server binary only
- `make frpc` - Build client binary only
- `make all` - Build everything with formatting

### Testing
- `make test` - Run unit tests
- `make e2e` - Run end-to-end tests
- `make e2e-trace` - Run e2e tests with trace logging
- `make alltest` - Run all tests including vet, unit tests, and e2e

### Code Quality
- `make fmt` - Run go fmt
- `make fmt-more` - Run gofumpt for more strict formatting
- `make gci` - Run gci import organizer
- `make vet` - Run go vet
- `golangci-lint run` - Run comprehensive linting (configured in .golangci.yml)

### Assets
- `make web` - Build web dashboards (frps and frpc)

### Cleanup
- `make clean` - Remove built binaries and temporary files

## Testing

- E2E tests using Ginkgo/Gomega framework
- Mock servers in `/test/e2e/mock/`
- Run: `make e2e` or `make alltest`


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.



================================================
FILE: Makefile
================================================
export PATH := $(PATH):`go env GOPATH`/bin
export GO111MODULE=on
LDFLAGS := -s -w
NOWEB_TAG = $(shell [ ! -d web/frps/dist ] || [ ! -d web/frpc/dist ] && echo ',noweb')

.PHONY: web frps-web frpc-web frps frpc

all: env fmt web build

build: frps frpc

env:
	@go version

web: frps-web frpc-web

frps-web:
	$(MAKE) -C web/frps build

frpc-web:
	$(MAKE) -C web/frpc build

fmt:
	go fmt ./...

fmt-more:
	gofumpt -l -w .

gci:
	gci write -s standard -s default -s "prefix(github.com/fatedier/frp/)" ./

vet:
	go vet -tags "$(NOWEB_TAG)" ./...

frps:
	env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags "frps$(NOWEB_TAG)" -o bin/frps ./cmd/frps

frpc:
	env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags "frpc$(NOWEB_TAG)" -o bin/frpc ./cmd/frpc

test: gotest

gotest:
	go test -tags "$(NOWEB_TAG)" -v --cover ./assets/...
	go test -tags "$(NOWEB_TAG)" -v --cover ./cmd/...
	go test -tags "$(NOWEB_TAG)" -v --cover ./client/...
	go test -tags "$(NOWEB_TAG)" -v --cover ./server/...
	go test -tags "$(NOWEB_TAG)" -v --cover ./pkg/...

e2e:
	./hack/run-e2e.sh

e2e-trace:
	DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh

e2e-compatibility-last-frpc:
	if [ ! -d "./lastversion" ]; then \
		TARGET_DIRNAME=lastversion ./hack/download.sh; \
	fi
	FRPC_PATH="`pwd`/lastversion/frpc" ./hack/run-e2e.sh
	rm -r ./lastversion

e2e-compatibility-last-frps:
	if [ ! -d "./lastversion" ]; then \
		TARGET_DIRNAME=lastversion ./hack/download.sh; \
	fi
	FRPS_PATH="`pwd`/lastversion/frps" ./hack/run-e2e.sh
	rm -r ./lastversion

alltest: vet gotest e2e
	
clean:
	rm -f ./bin/frpc
	rm -f ./bin/frps
	rm -rf ./lastversion


================================================
FILE: Makefile.cross-compiles
================================================
export PATH := $(PATH):`go env GOPATH`/bin
export GO111MODULE=on
LDFLAGS := -s -w

os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 openbsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 linux:loong64 android:arm64

all: build

build: app

app:
	@$(foreach n, $(os-archs), \
		os=$(shell echo "$(n)" | cut -d : -f 1); \
		arch=$(shell echo "$(n)" | cut -d : -f 2); \
		extra=$(shell echo "$(n)" | cut -d : -f 3); \
		flags=''; \
		target_suffix=$${os}_$${arch}; \
		if [ "$${os}" = "linux" ] && [ "$${arch}" = "arm" ] && [ "$${extra}" != "" ] ; then \
			if [ "$${extra}" = "7" ]; then \
				flags=GOARM=7; \
				target_suffix=$${os}_arm_hf; \
			elif [ "$${extra}" = "5" ]; then \
				flags=GOARM=5; \
				target_suffix=$${os}_arm; \
			fi; \
		elif [ "$${os}" = "linux" ] && ([ "$${arch}" = "mips" ] || [ "$${arch}" = "mipsle" ]) && [ "$${extra}" != "" ] ; then \
		    flags=GOMIPS=$${extra}; \
		fi; \
		echo "Build $${os}-$${arch}$${extra:+ ($${extra})}..."; \
		env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $${flags} go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o ./release/frpc_$${target_suffix} ./cmd/frpc; \
		env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} $${flags} go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o ./release/frps_$${target_suffix} ./cmd/frps; \
		echo "Build $${os}-$${arch}$${extra:+ ($${extra})} done"; \
	)
	@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
	@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
	@mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe
	@mv ./release/frps_windows_arm64 ./release/frps_windows_arm64.exe


================================================
FILE: README.md
================================================
# frp

[![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp)
[![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases)
[![Go Report Card](https://goreportcard.com/badge/github.com/fatedier/frp)](https://goreportcard.com/report/github.com/fatedier/frp)
[![GitHub Releases Stats](https://img.shields.io/github/downloads/fatedier/frp/total.svg?logo=github)](https://somsubhra.github.io/github-release-stats/?username=fatedier&repository=frp)

[README](README.md) | [中文文档](README_zh.md)

## Sponsors

frp is an open source project with its ongoing development made possible entirely by the support of our awesome sponsors. If you'd like to join them, please consider [sponsoring frp's development](https://github.com/sponsors/fatedier).

<h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start-->
<div align="center">

## Recall.ai - API for meeting recordings

If you're looking for a meeting recording API, consider checking out [Recall.ai](https://www.recall.ai/?utm_source=github&utm_medium=sponsorship&utm_campaign=fatedier-frp),

an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and more.

</div>

<p align="center">
  <a href="https://requestly.com/?utm_source=github&utm_medium=partnered&utm_campaign=frp" target="_blank">
    <img width="480px" src="https://github.com/user-attachments/assets/24670320-997d-4d62-9bca-955c59fe883d">
    <br>
    <b>Requestly - Free & Open-Source alternative to Postman</b>
    <br>
    <sub>All-in-one platform to Test, Mock and Intercept APIs.</sub>
  </a>
</p>

<p align="center">
  <a href="https://jb.gg/frp" target="_blank">
    <img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
	<br>
	<b>The complete IDE crafted for professional Go developers</b>
  </a>
</p>

<p align="center">
  <a href="https://github.com/beclab/Olares" target="_blank">
    <img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
	<br>
	<b>The sovereign cloud that puts you in control</b>
	<br>
	<sub>An open source, self-hosted alternative to public clouds, built for data ownership and privacy</sub>
  </a>
</p>
<!--gold sponsors end-->

## What is frp?

frp is a fast reverse proxy that allows you to expose a local server located behind a NAT or firewall to the Internet. It currently supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, enabling requests to be forwarded to internal services via domain name.

frp also offers a P2P connect mode.

## Table of Contents

<!-- vim-markdown-toc GFM -->

* [Development Status](#development-status)
    * [About V2](#about-v2)
* [Architecture](#architecture)
* [Example Usage](#example-usage)
    * [Access your computer in a LAN network via SSH](#access-your-computer-in-a-lan-network-via-ssh)
    * [Multiple SSH services sharing the same port](#multiple-ssh-services-sharing-the-same-port)
    * [Accessing Internal Web Services with Custom Domains in LAN](#accessing-internal-web-services-with-custom-domains-in-lan)
    * [Forward DNS query requests](#forward-dns-query-requests)
    * [Forward Unix Domain Socket](#forward-unix-domain-socket)
    * [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
    * [Enable HTTPS for a local HTTP(S) service](#enable-https-for-a-local-https-service)
    * [Expose your service privately](#expose-your-service-privately)
    * [P2P Mode](#p2p-mode)
* [Features](#features)
    * [Configuration Files](#configuration-files)
    * [Using Environment Variables](#using-environment-variables)
    * [Split Configures Into Different Files](#split-configures-into-different-files)
    * [Server Dashboard](#server-dashboard)
    * [Client Admin UI](#client-admin-ui)
    * [Monitor](#monitor)
        * [Prometheus](#prometheus)
    * [Authenticating the Client](#authenticating-the-client)
        * [Token Authentication](#token-authentication)
        * [OIDC Authentication](#oidc-authentication)
    * [Encryption and Compression](#encryption-and-compression)
        * [TLS](#tls)
    * [Hot-Reloading frpc configuration](#hot-reloading-frpc-configuration)
    * [Get proxy status from client](#get-proxy-status-from-client)
    * [Only allowing certain ports on the server](#only-allowing-certain-ports-on-the-server)
    * [Port Reuse](#port-reuse)
    * [Bandwidth Limit](#bandwidth-limit)
        * [For Each Proxy](#for-each-proxy)
    * [TCP Stream Multiplexing](#tcp-stream-multiplexing)
    * [Support KCP Protocol](#support-kcp-protocol)
    * [Support QUIC Protocol](#support-quic-protocol)
    * [Connection Pooling](#connection-pooling)
    * [Load balancing](#load-balancing)
    * [Service Health Check](#service-health-check)
    * [Rewriting the HTTP Host Header](#rewriting-the-http-host-header)
    * [Setting other HTTP Headers](#setting-other-http-headers)
    * [Get Real IP](#get-real-ip)
        * [HTTP X-Forwarded-For](#http-x-forwarded-for)
        * [Proxy Protocol](#proxy-protocol)
    * [Require HTTP Basic Auth (Password) for Web Services](#require-http-basic-auth-password-for-web-services)
    * [Custom Subdomain Names](#custom-subdomain-names)
    * [URL Routing](#url-routing)
    * [TCP Port Multiplexing](#tcp-port-multiplexing)
    * [Connecting to frps via PROXY](#connecting-to-frps-via-proxy)
    * [Port range mapping](#port-range-mapping)
    * [Client Plugins](#client-plugins)
    * [Server Manage Plugins](#server-manage-plugins)
    * [SSH Tunnel Gateway](#ssh-tunnel-gateway)
    * [Virtual Network (VirtualNet)](#virtual-network-virtualnet)
* [Feature Gates](#feature-gates)
    * [Available Feature Gates](#available-feature-gates)
    * [Enabling Feature Gates](#enabling-feature-gates)
    * [Feature Lifecycle](#feature-lifecycle)
* [Related Projects](#related-projects)
* [Contributing](#contributing)
* [Donation](#donation)
    * [GitHub Sponsors](#github-sponsors)
    * [PayPal](#paypal)

<!-- vim-markdown-toc -->

## Development Status

frp is currently under development. You can try the latest release version in the `master` branch, or use the `dev` branch to access the version currently in development.

We are currently working on version 2 and attempting to perform some code refactoring and improvements. However, please note that it will not be compatible with version 1.

We will transition from version 0 to version 1 at the appropriate time and will only accept bug fixes and improvements, rather than big feature requests.

### About V2

The complexity and difficulty of the v2 version are much higher than anticipated. I can only work on its development during fragmented time periods, and the constant interruptions disrupt productivity significantly. Given this situation, we will continue to optimize and iterate on the current version until we have more free time to proceed with the major version overhaul.

The concept behind v2 is based on my years of experience and reflection in the cloud-native domain, particularly in K8s and ServiceMesh. Its core is a modernized four-layer and seven-layer proxy, similar to envoy. This proxy itself is highly scalable, not only capable of implementing the functionality of intranet penetration but also applicable to various other domains. Building upon this highly scalable core, we aim to implement all the capabilities of frp v1 while also addressing the functionalities that were previously unachievable or difficult to implement in an elegant manner. Furthermore, we will maintain efficient development and iteration capabilities.

In addition, I envision frp itself becoming a highly extensible system and platform, similar to how we can provide a range of extension capabilities based on K8s. In K8s, we can customize development according to enterprise needs, utilizing features such as CRD, controller mode, webhook, CSI, and CNI. In frp v1, we introduced the concept of server plugins, which implemented some basic extensibility. However, it relies on a simple HTTP protocol and requires users to start independent processes and manage them on their own. This approach is far from flexible and convenient, and real-world demands vary greatly. It is unrealistic to expect a non-profit open-source project maintained by a few individuals to meet everyone's needs.

Finally, we acknowledge that the current design of modules such as configuration management, permission verification, certificate management, and API management is not modern enough. While we may carry out some optimizations in the v1 version, ensuring compatibility remains a challenging issue that requires a considerable amount of effort to address.

We sincerely appreciate your support for frp.

## Architecture

![architecture](/doc/pic/architecture.png)

## Example Usage

To begin, download the latest program for your operating system and architecture from the [Release](https://github.com/fatedier/frp/releases) page.

Next, place the `frps` binary and server configuration file on Server A, which has a public IP address.

Finally, place the `frpc` binary and client configuration file on Server B, which is located on a LAN that cannot be directly accessed from the public internet.

Some antiviruses improperly mark frpc as malware and delete it. This is due to frp being a networking tool capable of creating reverse proxies. Antiviruses sometimes flag reverse proxies due to their ability to bypass firewall port restrictions. If you are using antivirus, then you may need to whitelist/exclude frpc in your antivirus settings to avoid accidental quarantine/deletion. See [issue 3637](https://github.com/fatedier/frp/issues/3637) for more details.

### Access your computer in a LAN network via SSH

1. Modify `frps.toml` on server A by setting the `bindPort` for frp clients to connect to:

  ```toml
  # frps.toml
  bindPort = 7000
  ```

2. Start `frps` on server A:

  `./frps -c ./frps.toml`

3. Modify `frpc.toml` on server B and set the `serverAddr` field to the public IP address of your frps server:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "ssh"
  type = "tcp"
  localIP = "127.0.0.1"
  localPort = 22
  remotePort = 6000
  ```

Note that the `localPort` (listened on the client) and `remotePort` (exposed on the server) are used for traffic going in and out of the frp system, while the `serverPort` is used for communication between frps and frpc.

4. Start `frpc` on server B:

  `./frpc -c ./frpc.toml`

5. To access server B from another machine through server A via SSH (assuming the username is `test`), use the following command:

  `ssh -oPort=6000 test@x.x.x.x`

### Multiple SSH services sharing the same port

This example implements multiple SSH services exposed through the same port using a proxy of type tcpmux. Similarly, as long as the client supports the HTTP Connect proxy connection method, port reuse can be achieved in this way.

1. Deploy frps on a machine with a public IP and modify the frps.toml file. Here is a simplified configuration:

  ```toml
  bindPort = 7000
  tcpmuxHTTPConnectPort = 5002
  ```

2. Deploy frpc on the internal machine A with the following configuration:

  ```toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "ssh1"
  type = "tcpmux"
  multiplexer = "httpconnect"
  customDomains = ["machine-a.example.com"]
  localIP = "127.0.0.1"
  localPort = 22
  ```

3. Deploy another frpc on the internal machine B with the following configuration:

  ```toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "ssh2"
  type = "tcpmux"
  multiplexer = "httpconnect"
  customDomains = ["machine-b.example.com"]
  localIP = "127.0.0.1"
  localPort = 22
  ```

4. To access internal machine A using SSH ProxyCommand, assuming the username is "test":

  `ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' test@machine-a.example.com`

5. To access internal machine B, the only difference is the domain name, assuming the username is "test":

  `ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' test@machine-b.example.com`

### Accessing Internal Web Services with Custom Domains in LAN

Sometimes we need to expose a local web service behind a NAT network to others for testing purposes with our own domain name.

Unfortunately, we cannot resolve a domain name to a local IP. However, we can use frp to expose an HTTP(S) service.

1. Modify `frps.toml` and set the HTTP port for vhost to 8080:

  ```toml
  # frps.toml
  bindPort = 7000
  vhostHTTPPort = 8080
  ```

  If you want to configure an https proxy, you need to set up the `vhostHTTPSPort`.

2. Start `frps`:

  `./frps -c ./frps.toml`

3. Modify `frpc.toml` and set `serverAddr` to the IP address of the remote frps server. Specify the `localPort` of your web service:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "web"
  type = "http"
  localPort = 80
  customDomains = ["www.example.com"]
  ```

4. Start `frpc`:

  `./frpc -c ./frpc.toml`

5. Map the A record of `www.example.com` to either the public IP of the remote frps server or a CNAME record pointing to your original domain.

6. Visit your local web service using url `http://www.example.com:8080`.

### Forward DNS query requests

1. Modify `frps.toml`:

  ```toml
  # frps.toml
  bindPort = 7000
  ```

2. Start `frps`:

  `./frps -c ./frps.toml`

3. Modify `frpc.toml` and set `serverAddr` to the IP address of the remote frps server. Forward DNS query requests to the Google Public DNS server `8.8.8.8:53`:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "dns"
  type = "udp"
  localIP = "8.8.8.8"
  localPort = 53
  remotePort = 6000
  ```

4. Start frpc:

  `./frpc -c ./frpc.toml`

5. Test DNS resolution using the `dig` command:

  `dig @x.x.x.x -p 6000 www.google.com`

### Forward Unix Domain Socket

Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP.

Configure `frps` as above.

1. Start `frpc` with the following configuration:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "unix_domain_socket"
  type = "tcp"
  remotePort = 6000
  [proxies.plugin]
  type = "unix_domain_socket"
  unixPath = "/var/run/docker.sock"
  ```

2. Test the configuration by getting the docker version using `curl`:

  `curl http://x.x.x.x:6000/version`

### Expose a simple HTTP file server

Expose a simple HTTP file server to access files stored in the LAN from the public Internet.

Configure `frps` as described above, then:

1. Start `frpc` with the following configuration:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "test_static_file"
  type = "tcp"
  remotePort = 6000
  [proxies.plugin]
  type = "static_file"
  localPath = "/tmp/files"
  stripPrefix = "static"
  httpUser = "abc"
  httpPassword = "abc"
  ```

2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct username and password to view files in `/tmp/files` on the `frpc` machine.

### Enable HTTPS for a local HTTP(S) service

You may substitute `https2https` for the plugin, and point the `localAddr` to a HTTPS endpoint.

1. Start `frpc` with the following configuration:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "test_https2http"
  type = "https"
  customDomains = ["test.example.com"]

  [proxies.plugin]
  type = "https2http"
  localAddr = "127.0.0.1:80"
  crtPath = "./server.crt"
  keyPath = "./server.key"
  hostHeaderRewrite = "127.0.0.1"
  requestHeaders.set.x-from-where = "frp"
  ```

2. Visit `https://test.example.com`.

### Expose your service privately

To mitigate risks associated with exposing certain services directly to the public network, STCP (Secret TCP) mode requires a preshared key to be used for access to the service from other clients.

Configure `frps` same as above.

1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `secretKey` field for the preshared key, and that the `remotePort` field is removed here:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[proxies]]
  name = "secret_ssh"
  type = "stcp"
  secretKey = "abcdefg"
  localIP = "127.0.0.1"
  localPort = 22
  ```

2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`secretKey` field):

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000

  [[visitors]]
  name = "secret_ssh_visitor"
  type = "stcp"
  serverName = "secret_ssh"
  secretKey = "abcdefg"
  bindAddr = "127.0.0.1"
  bindPort = 6000
  ```

3. On machine C, connect to SSH on machine B, using this command:

  `ssh -oPort=6000 127.0.0.1`

### P2P Mode

**xtcp** is designed to transmit large amounts of data directly between clients. A frps server is still needed, as P2P here only refers to the actual data transmission.

Note that it may not work with all types of NAT devices. You might want to fallback to stcp if xtcp doesn't work.

1. Start `frpc` on machine B, and expose the SSH port. Note that the `remotePort` field is removed:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000
  # set up a new stun server if the default one is not available.
  # natHoleStunServer = "xxx"

  [[proxies]]
  name = "p2p_ssh"
  type = "xtcp"
  secretKey = "abcdefg"
  localIP = "127.0.0.1"
  localPort = 22
  ```

2. Start another `frpc` (typically on another machine C) with the configuration to connect to SSH using P2P mode:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  serverPort = 7000
  # set up a new stun server if the default one is not available.
  # natHoleStunServer = "xxx"

  [[visitors]]
  name = "p2p_ssh_visitor"
  type = "xtcp"
  serverName = "p2p_ssh"
  secretKey = "abcdefg"
  bindAddr = "127.0.0.1"
  bindPort = 6000
  # when automatic tunnel persistence is required, set it to true
  keepTunnelOpen = false
  ```

3. On machine C, connect to SSH on machine B, using this command:

  `ssh -oPort=6000 127.0.0.1`

## Features

### Configuration Files

Since v0.52.0, we support TOML, YAML, and JSON for configuration. Please note that INI is deprecated and will be removed in future releases. New features will only be available in TOML, YAML, or JSON. Users wanting these new features should switch their configuration format accordingly.

Read the full example configuration files to find out even more features not described here.

Examples use TOML format, but you can still use YAML or JSON.

These configuration files is for reference only. Please do not use this configuration directly to run the program as it may have various issues.

[Full configuration file for frps (Server)](./conf/frps_full_example.toml)

[Full configuration file for frpc (Client)](./conf/frpc_full_example.toml)

### Using Environment Variables

Environment variables can be referenced in the configuration file, using Go's standard format:

```toml
# frpc.toml
serverAddr = "{{ .Envs.FRP_SERVER_ADDR }}"
serverPort = 7000

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = {{ .Envs.FRP_SSH_REMOTE_PORT }}
```

With the config above, variables can be passed into `frpc` program like this:

```
export FRP_SERVER_ADDR=x.x.x.x
export FRP_SSH_REMOTE_PORT=6000
./frpc -c ./frpc.toml
```

`frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`.

### Split Configures Into Different Files

You can split multiple proxy configs into different files and include them in the main file.

```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000
includes = ["./confd/*.toml"]
```

```toml
# ./confd/test.toml

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
```

### Server Dashboard

Check frp's status and proxies' statistics information by Dashboard.

Configure a port for dashboard to enable this feature:

```toml
# The default value is 127.0.0.1. Change it to 0.0.0.0 when you want to access it from a public network.
webServer.addr = "0.0.0.0"
webServer.port = 7500
# dashboard's username and password are both optional
webServer.user = "admin"
webServer.password = "admin"
```

Then visit `http://[serverAddr]:7500` to see the dashboard, with username and password both being `admin`.

Additionally, you can use HTTPS port by using your domains wildcard or normal SSL certificate:

```toml
webServer.port = 7500
# dashboard's username and password are both optional
webServer.user = "admin"
webServer.password = "admin"
webServer.tls.certFile = "server.crt"
webServer.tls.keyFile = "server.key"
```

Then visit `https://[serverAddr]:7500` to see the dashboard in secure HTTPS connection, with username and password both being `admin`.

![dashboard](/doc/pic/dashboard.png)

### Client Admin UI

The Client Admin UI helps you check and manage frpc's configuration.

Configure an address for admin UI to enable this feature:

```toml
webServer.addr = "127.0.0.1"
webServer.port = 7400
webServer.user = "admin"
webServer.password = "admin"
```

Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin`.

### Monitor

When web server is enabled, frps will save monitor data in cache for 7 days. It will be cleared after process restart.

Prometheus is also supported.

#### Prometheus

Enable dashboard first, then configure `enablePrometheus = true` in `frps.toml`.

`http://{dashboard_addr}/metrics` will provide prometheus monitor data.

### Authenticating the Client

There are 2 authentication methods to authenticate frpc with frps. 

You can decide which one to use by configuring `auth.method` in `frpc.toml` and `frps.toml`, the default one is token.

Configuring `auth.additionalScopes = ["HeartBeats"]` will use the configured authentication method to add and validate authentication on every heartbeat between frpc and frps.

Configuring `auth.additionalScopes = ["NewWorkConns"]` will do the same for every new work connection between frpc and frps.

#### Token Authentication

When specifying `auth.method = "token"` in `frpc.toml` and `frps.toml` - token based authentication will be used.

Make sure to specify the same `auth.token` in `frps.toml` and `frpc.toml` for frpc to pass frps validation

##### Token Source

frp supports reading authentication tokens from external sources using the `tokenSource` configuration. Currently, file-based token source is supported.

**File-based token source:**

```toml
# frpc.toml
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/path/to/token/file"
```

The token will be read from the specified file at startup. This is useful for scenarios where tokens are managed by external systems or need to be kept separate from configuration files for security reasons.

#### OIDC Authentication

When specifying `auth.method = "oidc"` in `frpc.toml` and `frps.toml` - OIDC based authentication will be used.

OIDC stands for OpenID Connect, and the flow used is called [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).

To use this authentication type - configure `frpc.toml` and `frps.toml` as follows:

```toml
# frps.toml
auth.method = "oidc"
auth.oidc.issuer = "https://example-oidc-issuer.com/"
auth.oidc.audience = "https://oidc-audience.com/.default"
```

```toml
# frpc.toml
auth.method = "oidc"
auth.oidc.clientID = "98692467-37de-409a-9fac-bb2585826f18" # Replace with OIDC client ID
auth.oidc.clientSecret = "oidc_secret"
auth.oidc.audience = "https://oidc-audience.com/.default"
auth.oidc.tokenEndpointURL = "https://example-oidc-endpoint.com/oauth2/v2.0/token"
```

### Encryption and Compression

The features are off by default. You can turn on encryption and/or compression:

```toml
# frpc.toml

[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 6000
transport.useEncryption = true
transport.useCompression = true
```

#### TLS

Since v0.50.0, the default value of `transport.tls.enable` and `transport.tls.disableCustomTLSFirstByte` has been changed to true, and tls is enabled by default.

For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection. This only takes effect when you set `transport.tls.disableCustomTLSFirstByte` to false.

To **enforce** `frps` to only accept TLS connections - configure `transport.tls.force = true` in `frps.toml`. **This is optional.**

**`frpc` TLS settings:**

```toml
transport.tls.enable = true
transport.tls.certFile = "certificate.crt"
transport.tls.keyFile = "certificate.key"
transport.tls.trustedCaFile = "ca.crt"
```

**`frps` TLS settings:**

```toml
transport.tls.force = true
transport.tls.certFile = "certificate.crt"
transport.tls.keyFile = "certificate.key"
transport.tls.trustedCaFile = "ca.crt"
```

You will need **a root CA cert** and **at least one SSL/TLS certificate**. It **can** be self-signed or regular (such as Let's Encrypt or another SSL/TLS certificate provider).

If you using `frp` via IP address and not hostname, make sure to set the appropriate IP address in the Subject Alternative Name (SAN) area when generating SSL/TLS Certificates.

Given an example:

* Prepare openssl config file. It exists at `/etc/pki/tls/openssl.cnf` in Linux System and `/System/Library/OpenSSL/openssl.cnf` in MacOS, and you can copy it to current path, like `cp /etc/pki/tls/openssl.cnf ./my-openssl.cnf`. If not, you can build it by yourself, like:
```
cat > my-openssl.cnf << EOF
[ ca ]
default_ca = CA_default
[ CA_default ]
x509_extensions = usr_cert
[ req ]
default_bits        = 2048
default_md          = sha256
default_keyfile     = privkey.pem
distinguished_name  = req_distinguished_name
attributes          = req_attributes
x509_extensions     = v3_ca
string_mask         = utf8only
[ req_distinguished_name ]
[ req_attributes ]
[ usr_cert ]
basicConstraints       = CA:FALSE
nsComment              = "OpenSSL Generated Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = CA:true
EOF
```

* build ca certificates:
```
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt
```

* build frps certificates:
```
openssl genrsa -out server.key 2048

openssl req -new -sha256 -key server.key \
    -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \
    -reqexts SAN \
    -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com")) \
    -out server.csr

openssl x509 -req -days 365 -sha256 \
	-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
	-extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com") \
	-out server.crt
```

* build frpc certificates:
```
openssl genrsa -out client.key 2048
openssl req -new -sha256 -key client.key \
    -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com" \
    -reqexts SAN \
    -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:client.com,DNS:example.client.com")) \
    -out client.csr

openssl x509 -req -days 365 -sha256 \
    -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
	-extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com") \
	-out client.crt
```

### Hot-Reloading frpc configuration

The `webServer` fields are required for enabling HTTP API:

```toml
# frpc.toml
webServer.addr = "127.0.0.1"
webServer.port = 7400
```

Then run command `frpc reload -c ./frpc.toml` and wait for about 10 seconds to let `frpc` create or update or remove proxies.

**Note that global client parameters won't be modified except 'start'.**

`start` is a global allowlist evaluated after all sources are merged (config file/include/store).
If `start` is non-empty, any proxy or visitor not listed there will not be started, including
entries created via Store API.

`start` is kept mainly for compatibility and is generally not recommended for new configurations.
Prefer per-proxy/per-visitor `enabled`, and keep `start` empty unless you explicitly want this
global allowlist behavior.

You can run command `frpc verify -c ./frpc.toml` before reloading to check if there are config errors.

### Get proxy status from client

Use `frpc status -c ./frpc.toml` to get status of all proxies. The `webServer` fields are required for enabling HTTP API.

### Only allowing certain ports on the server

`allowPorts` in `frps.toml` is used to avoid abuse of ports:

```toml
# frps.toml
allowPorts = [
  { start = 2000, end = 3000 },
  { single = 3001 },
  { single = 3003 },
  { start = 4000, end = 50000 }
]
```

### Port Reuse

`vhostHTTPPort` and `vhostHTTPSPort` in frps can use same port with `bindPort`. frps will detect the connection's protocol and handle it correspondingly.

What you need to pay attention to is that if you want to configure `vhostHTTPSPort` and `bindPort` to the same port, you need to first set `transport.tls.disableCustomTLSFirstByte` to false.

We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.

### Bandwidth Limit

#### For Each Proxy

```toml
# frpc.toml

[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 6000
transport.bandwidthLimit = "1MB"
```

Set `transport.bandwidthLimit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`.

Set `transport.bandwidthLimitMode` to `client` or `server` to limit bandwidth on the client or server side. Default is `client`.

### TCP Stream Multiplexing

frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.

You can disable this feature by modify `frps.toml` and `frpc.toml`:

```toml
# frps.toml and frpc.toml, must be same
transport.tcpMux = false
```

### Support KCP Protocol

KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.

KCP mode uses UDP as the underlying transport. Using KCP in frp:

1. Enable KCP in frps:

  ```toml
  # frps.toml
  bindPort = 7000
  # Specify a UDP port for KCP.
  kcpBindPort = 7000
  ```

  The `kcpBindPort` number can be the same number as `bindPort`, since `bindPort` field specifies a TCP port.

2. Configure `frpc.toml` to use KCP to connect to frps:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  # Same as the 'kcpBindPort' in frps.toml
  serverPort = 7000
  transport.protocol = "kcp"
  ```

### Support QUIC Protocol

QUIC is a new multiplexed transport built on top of UDP.

Using QUIC in frp:

1. Enable QUIC in frps:

  ```toml
  # frps.toml
  bindPort = 7000
  # Specify a UDP port for QUIC.
  quicBindPort = 7000
  ```

  The `quicBindPort` number can be the same number as `bindPort`, since `bindPort` field specifies a TCP port.

2. Configure `frpc.toml` to use QUIC to connect to frps:

  ```toml
  # frpc.toml
  serverAddr = "x.x.x.x"
  # Same as the 'quicBindPort' in frps.toml
  serverPort = 7000
  transport.protocol = "quic"
  ```

### Connection Pooling

By default, frps creates a new frpc connection to the backend service upon a user request. With connection pooling, frps keeps a certain number of pre-established connections, reducing the time needed to establish a connection.

This feature is suitable for a large number of short connections.

1. Configure the limit of pool count each proxy can use in `frps.toml`:

  ```toml
  # frps.toml
  transport.maxPoolCount = 5
  ```

2. Enable and specify the number of connection pool:

  ```toml
  # frpc.toml
  transport.poolCount = 1
  ```

### Load balancing

Load balancing is supported by `group`.

This feature is only available for types `tcp`, `http`, `tcpmux` now.

```toml
# frpc.toml

[[proxies]]
name = "test1"
type = "tcp"
localPort = 8080
remotePort = 80
loadBalancer.group = "web"
loadBalancer.groupKey = "123"

[[proxies]]
name = "test2"
type = "tcp"
localPort = 8081
remotePort = 80
loadBalancer.group = "web"
loadBalancer.groupKey = "123"
```

`loadBalancer.groupKey` is used for authentication.

Connections to port 80 will be dispatched to proxies in the same group randomly.

For type `tcp`, `remotePort` in the same group should be the same.

For type `http`, `customDomains`, `subdomain`, `locations` should be the same.

### Service Health Check

Health check feature can help you achieve high availability with load balancing.

Add `healthCheck.type = "tcp"` or `healthCheck.type = "http"` to enable health check.

With health check type **tcp**, the service port will be pinged (TCPing):

```toml
# frpc.toml

[[proxies]]
name = "test1"
type = "tcp"
localPort = 22
remotePort = 6000
# Enable TCP health check
healthCheck.type = "tcp"
# TCPing timeout seconds
healthCheck.timeoutSeconds = 3
# If health check failed 3 times in a row, the proxy will be removed from frps
healthCheck.maxFailed = 3
# A health check every 10 seconds
healthCheck.intervalSeconds = 10
```

With health check type **http**, an HTTP request will be sent to the service and an HTTP 2xx OK response is expected:

```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localIP = "127.0.0.1"
localPort = 80
customDomains = ["test.example.com"]
# Enable HTTP health check
healthCheck.type = "http"
# frpc will send a GET request to '/status'
# and expect an HTTP 2xx OK response
healthCheck.path = "/status"
healthCheck.timeoutSeconds = 3
healthCheck.maxFailed = 3
healthCheck.intervalSeconds = 10
```

### Rewriting the HTTP Host Header

By default frp does not modify the tunneled HTTP requests at all as it's a byte-for-byte copy.

However, speaking of web servers and HTTP requests, your web server might rely on the `Host` HTTP header to determine the website to be accessed. frp can rewrite the `Host` header when forwarding the HTTP requests, with the `hostHeaderRewrite` field:

```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
customDomains = ["test.example.com"]
hostHeaderRewrite = "dev.example.com"
```

The HTTP request will have the `Host` header rewritten to `Host: dev.example.com` when it reaches the actual web server, although the request from the browser probably has `Host: test.example.com`.

### Setting other HTTP Headers

Similar to `Host`, You can override other HTTP request and response headers with proxy type `http`.

```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
customDomains = ["test.example.com"]
hostHeaderRewrite = "dev.example.com"
requestHeaders.set.x-from-where = "frp"
responseHeaders.set.foo = "bar"
```

In this example, it will set header `x-from-where: frp` in the HTTP request and `foo: bar` in the HTTP response.

### Get Real IP

#### HTTP X-Forwarded-For

This feature is for `http` proxies or proxies with the `https2http` and `https2https` plugins enabled.

You can get user's real IP from HTTP request headers `X-Forwarded-For`.

#### Proxy Protocol

frp supports Proxy Protocol to send user's real IP to local services.

Here is an example for https service:

```toml
# frpc.toml

[[proxies]]
name = "web"
type = "https"
localPort = 443
customDomains = ["test.example.com"]

# now v1 and v2 are supported
transport.proxyProtocolVersion = "v2"
```

You can enable Proxy Protocol support in nginx to expose user's real IP in HTTP header `X-Real-IP`, and then read `X-Real-IP` header in your web service for the real IP.

### Require HTTP Basic Auth (Password) for Web Services

Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.

This enforces HTTP Basic Auth on all requests with the username and password specified in frpc's configure file.

It can only be enabled when proxy type is http.

```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
customDomains = ["test.example.com"]
httpUser = "abc"
httpPassword = "abc"
```

Visit `http://test.example.com` in the browser and now you are prompted to enter the username and password.

### Custom Subdomain Names

It is convenient to use `subdomain` configure for http and https types when many people share one frps server.

```toml
# frps.toml
subDomainHost = "frps.com"
```

Resolve `*.frps.com` to the frps server's IP. This is usually called a Wildcard DNS record.

```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
subdomain = "test"
```

Now you can visit your web service on `test.frps.com`.

Note that if `subdomainHost` is not empty, `customDomains` should not be the subdomain of `subdomainHost`.

### URL Routing

frp supports forwarding HTTP requests to different backend web services by url routing.

`locations` specifies the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.

```toml
# frpc.toml

[[proxies]]
name = "web01"
type = "http"
localPort = 80
customDomains = ["web.example.com"]
locations = ["/"]

[[proxies]]
name = "web02"
type = "http"
localPort = 81
customDomains = ["web.example.com"]
locations = ["/news", "/about"]
```

HTTP requests with URL prefix `/news` or `/about` will be forwarded to **web02** and other requests to **web01**.

### TCP Port Multiplexing

frp supports receiving TCP sockets directed to different proxies on a single port on frps, similar to `vhostHTTPPort` and `vhostHTTPSPort`.

The only supported TCP port multiplexing method available at the moment is `httpconnect` - HTTP CONNECT tunnel.

When setting `tcpmuxHTTPConnectPort` to anything other than 0 in frps, frps will listen on this port for HTTP CONNECT requests.

The host of the HTTP CONNECT request will be used to match the proxy in frps. Proxy hosts can be configured in frpc by configuring `customDomains` and / or `subdomain` under `tcpmux` proxies, when `multiplexer = "httpconnect"`.

For example:

```toml
# frps.toml
bindPort = 7000
tcpmuxHTTPConnectPort = 1337
```

```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "proxy1"
type = "tcpmux"
multiplexer = "httpconnect"
customDomains = ["test1"]
localPort = 80

[[proxies]]
name = "proxy2"
type = "tcpmux"
multiplexer = "httpconnect"
customDomains = ["test2"]
localPort = 8080
```

In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:

```
CONNECT test1 HTTP/1.1\r\n\r\n
```
and the connection will be routed to `proxy1`.

### Connecting to frps via PROXY

frpc can connect to frps through proxy if you set OS environment variable `HTTP_PROXY`, or if `transport.proxyURL` is set in frpc.toml file.

It only works when protocol is tcp.

```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000
transport.proxyURL = "http://user:pwd@192.168.1.128:8080"
```

### Port range mapping

*Added in v0.56.0*

We can use the range syntax of Go template combined with the built-in `parseNumberRangePair` function to achieve port range mapping.

The following example, when run, will create 8 proxies named `test-6000, test-6001 ... test-6007`, each mapping the remote port to the local port.

```
{{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }}
[[proxies]]
name = "tcp-{{ $v.First }}"
type = "tcp"
localPort = {{ $v.First }}
remotePort = {{ $v.Second }}
{{- end }}
```

### Client Plugins

frpc only forwards requests to local TCP or UDP ports by default.

Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file`, `http2https`, `https2http`, `https2https` and you can see [example usage](#example-usage).

Using plugin **http_proxy**:

```toml
# frpc.toml

[[proxies]]
name = "http_proxy"
type = "tcp"
remotePort = 6000
[proxies.plugin]
type = "http_proxy"
httpUser = "abc"
httpPassword = "abc"
```

`httpUser` and `httpPassword` are configuration parameters used in `http_proxy` plugin.

### Server Manage Plugins

Read the [document](/doc/server_plugin.md).

Find more plugins in [gofrp/plugin](https://github.com/gofrp/plugin).

### SSH Tunnel Gateway

*added in v0.53.0*

frp supports listening to an SSH port on the frps side and achieves TCP protocol proxying through the SSH -R protocol, without relying on frpc.

```toml
# frps.toml
sshTunnelGateway.bindPort = 2200
```

When running `./frps -c frps.toml`, a private key file named `.autogen_ssh_key` will be automatically created in the current working directory. This generated private key file will be used by the SSH server in frps.

Executing the command

```bash
ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 tcp --proxy_name "test-tcp" --remote_port 9090
```

sets up a proxy on frps that forwards the local 8080 service to the port 9090.

```bash
frp (via SSH) (Ctrl+C to quit)

User:
ProxyName: test-tcp
Type: tcp
RemoteAddress: :9090
```

This is equivalent to:

```bash
frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote_port 9090
```

Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.

### Virtual Network (VirtualNet)

*Alpha feature added in v0.62.0*

The VirtualNet feature enables frp to create and manage virtual network connections between clients and visitors through a TUN interface. This allows for IP-level routing between machines, extending frp beyond simple port forwarding to support full network connectivity.

For detailed information about configuration and usage, please refer to the [VirtualNet documentation](/doc/virtual_net.md).

## Feature Gates

frp supports feature gates to enable or disable experimental features. This allows users to try out new features before they're considered stable.

### Available Feature Gates

| Name | Stage | Default | Description |
|------|-------|---------|-------------|
| VirtualNet | ALPHA | false | Virtual network capabilities for frp |

### Enabling Feature Gates

To enable an experimental feature, add the feature gate to your configuration:

```toml
featureGates = { VirtualNet = true }
```

### Feature Lifecycle

Features typically go through three stages:
1. **ALPHA**: Disabled by default, may be unstable
2. **BETA**: May be enabled by default, more stable but still evolving
3. **GA (Generally Available)**: Enabled by default, ready for production use

## Related Projects

* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - A lightweight version of the frp client (around 3.5MB at minimum) implemented using the ssh protocol, supporting some of the most commonly used features, suitable for devices with limited resources.

## Contributing

Interested in getting involved? We would like to help you!

* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
* Sorry for my poor English. Improvements for this document are welcome, even some typo fixes.
* If you have great ideas, send an email to fatedier@gmail.com.

**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatedly.**

## Donation

If frp helps you a lot, you can support us by:

### GitHub Sponsors

Support us by [Github Sponsors](https://github.com/sponsors/fatedier).

You can have your company's logo placed on README file of this project.

### PayPal

Donate money by [PayPal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.


================================================
FILE: README_zh.md
================================================
# frp

[![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp)
[![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases)
[![Go Report Card](https://goreportcard.com/badge/github.com/fatedier/frp)](https://goreportcard.com/report/github.com/fatedier/frp)
[![GitHub Releases Stats](https://img.shields.io/github/downloads/fatedier/frp/total.svg?logo=github)](https://somsubhra.github.io/github-release-stats/?username=fatedier&repository=frp)

[README](README.md) | [中文文档](README_zh.md)

frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。

## Sponsors

frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者们的支持。如果你愿意加入他们的行列,请考虑 [赞助 frp 的开发](https://github.com/sponsors/fatedier)。

<h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start-->
<div align="center">

## Recall.ai - API for meeting recordings

If you're looking for a meeting recording API, consider checking out [Recall.ai](https://www.recall.ai/?utm_source=github&utm_medium=sponsorship&utm_campaign=fatedier-frp),

an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and more.

</div>

<p align="center">
  <a href="https://requestly.com/?utm_source=github&utm_medium=partnered&utm_campaign=frp" target="_blank">
    <img width="480px" src="https://github.com/user-attachments/assets/24670320-997d-4d62-9bca-955c59fe883d">
    <br>
    <b>Requestly - Free & Open-Source alternative to Postman</b>
    <br>
    <sub>All-in-one platform to Test, Mock and Intercept APIs.</sub>
  </a>
</p>

<p align="center">
  <a href="https://jb.gg/frp" target="_blank">
    <img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
	<br>
	<b>The complete IDE crafted for professional Go developers</b>
  </a>
</p>

<p align="center">
  <a href="https://github.com/beclab/Olares" target="_blank">
    <img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
	<br>
	<b>The sovereign cloud that puts you in control</b>
	<br>
	<sub>An open source, self-hosted alternative to public clouds, built for data ownership and privacy</sub>
  </a>
</p>
<!--gold sponsors end-->

## 为什么使用 frp ?

通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:

* 客户端服务端通信支持 TCP、QUIC、KCP 以及 Websocket 等多种协议。
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间,降低请求延迟。
* 代理组间的负载均衡。
* 端口复用,多个服务通过同一个服务端端口暴露。
* 支持 P2P 通信,流量不经过服务器中转,充分利用带宽资源。
* 多个原生支持的客户端插件(静态文件查看,HTTPS/HTTP 协议转换,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
* 高度扩展性的服务端插件系统,易于结合自身需求进行功能扩展。
* 服务端和客户端 UI 页面。

## 开发状态

frp 目前已被很多公司广泛用于测试、生产环境。

master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。

我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续较长的一段时间。

现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。

### 关于 v2 的一些说明

v2 版本的复杂度和难度比我们预期的要高得多。我只能利用零散的时间进行开发,而且由于上下文经常被打断,效率极低。由于这种情况可能会持续一段时间,我们仍然会在当前版本上进行一些优化和迭代,直到我们有更多空闲时间来推进大版本的重构,或者也有可能放弃一次性的重构,而是采用渐进的方式在当前版本上逐步做一些可能会导致不兼容的修改。

v2 的构想是基于我多年在云原生领域,特别是在 K8s 和 ServiceMesh 方面的工作经验和思考。它的核心是一个现代化的四层和七层代理,类似于 envoy。这个代理本身高度可扩展,不仅可以用于实现内网穿透的功能,还可以应用于更多领域。在这个高度可扩展的内核基础上,我们将实现 frp v1 中的所有功能,并且能够以一种更加优雅的方式实现原先架构中无法实现或不易实现的功能。同时,我们将保持高效的开发和迭代能力。

除此之外,我希望 frp 本身也成为一个高度可扩展的系统和平台,就像我们可以基于 K8s 提供一系列扩展能力一样。在 K8s 上,我们可以根据企业需求进行定制化开发,例如使用 CRD、controller 模式、webhook、CSI 和 CNI 等。在 frp v1 中,我们引入了服务端插件的概念,实现了一些简单的扩展性。但是,它实际上依赖于简单的 HTTP 协议,并且需要用户自己启动独立的进程和管理。这种方式远远不够灵活和方便,而且现实世界的需求千差万别,我们不能期望一个由少数人维护的非营利性开源项目能够满足所有人的需求。

最后,我们意识到像配置管理、权限验证、证书管理和管理 API 等模块的当前设计并不够现代化。尽管我们可能在 v1 版本中进行一些优化,但确保兼容性是一个令人头疼的问题,需要投入大量精力来解决。

非常感谢您对 frp 的支持。

## 文档

完整文档已经迁移至 [https://gofrp.org](https://gofrp.org)。

## 为 frp 做贡献

frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。

* 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。
* Bug 的修复可以直接提交 Pull Request 到 dev 分支。
* 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
* 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
* 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。

**提醒:和项目相关的问题请在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**

## 关联项目

* [gofrp/plugin](https://github.com/gofrp/plugin) - frp 插件仓库,收录了基于 frp 扩展机制实现的各种插件,满足各种场景下的定制化需求。
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - 基于 ssh 协议实现的 frp 客户端的精简版本(最低约 3.5MB 左右),支持常用的部分功能,适用于资源有限的设备。

## 赞助

如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。

### Sponsors

长期赞助可以帮助我们保持项目的持续发展。

您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。

国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。

企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。


================================================
FILE: Release.md
================================================
## Features

* Added a built-in `store` capability for frpc, including persisted store source (`[store] path = "..."`), Store CRUD admin APIs (`/api/store/proxies*`, `/api/store/visitors*`) with runtime reload, and Store management pages in the frpc web dashboard.

## Improvements

* Kept proxy/visitor names as raw config names during completion; moved user-prefix handling to explicit wire-level naming logic.
* Added `noweb` build tag to allow compiling without frontend assets. `make build` now auto-detects missing `web/*/dist` directories and skips embedding, so a fresh clone can build without running `make web` first. The dashboard gracefully returns 404 when assets are not embedded.
* Improved config parsing errors: for `.toml` files, syntax errors now return immediately with parser position details (line/column when available) instead of falling through to YAML/JSON parsing, and TOML type mismatches report field-level errors without misleading line numbers.
* OIDC auth now caches the access token and refreshes it before expiry, avoiding a new token request on every heartbeat. Falls back to per-request fetch when the provider omits `expires_in`.


================================================
FILE: assets/assets.go
================================================
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package assets

import (
	"io/fs"
	"net/http"
)

var (
	// read-only filesystem created by "embed" for embedded files
	content fs.FS

	FileSystem http.FileSystem

	// if prefix is not empty, we get file content from disk
	prefixPath string
)

type emptyFS struct{}

func (emptyFS) Open(name string) (http.File, error) {
	return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
}

// if path is empty, load assets in memory
// or set FileSystem using disk files
func Load(path string) {
	prefixPath = path
	switch {
	case prefixPath != "":
		FileSystem = http.Dir(prefixPath)
	case content != nil:
		FileSystem = http.FS(content)
	default:
		FileSystem = emptyFS{}
	}
}

func Register(fileSystem fs.FS) {
	subFs, err := fs.Sub(fileSystem, "dist")
	if err == nil {
		content = subFs
	}
}


================================================
FILE: client/api_router.go
================================================
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"net/http"

	adminapi "github.com/fatedier/frp/client/http"
	"github.com/fatedier/frp/client/proxy"
	httppkg "github.com/fatedier/frp/pkg/util/http"
	netpkg "github.com/fatedier/frp/pkg/util/net"
)

func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
	apiController := newAPIController(svr)

	// Healthz endpoint without auth
	helper.Router.HandleFunc("/healthz", healthz)

	// API routes and static files with auth
	subRouter := helper.Router.NewRoute().Subrouter()
	subRouter.Use(helper.AuthMiddleware)
	subRouter.Use(httppkg.NewRequestLogger)
	subRouter.HandleFunc("/api/reload", httppkg.MakeHTTPHandlerFunc(apiController.Reload)).Methods(http.MethodGet)
	subRouter.HandleFunc("/api/stop", httppkg.MakeHTTPHandlerFunc(apiController.Stop)).Methods(http.MethodPost)
	subRouter.HandleFunc("/api/status", httppkg.MakeHTTPHandlerFunc(apiController.Status)).Methods(http.MethodGet)
	subRouter.HandleFunc("/api/config", httppkg.MakeHTTPHandlerFunc(apiController.GetConfig)).Methods(http.MethodGet)
	subRouter.HandleFunc("/api/config", httppkg.MakeHTTPHandlerFunc(apiController.PutConfig)).Methods(http.MethodPut)
	subRouter.HandleFunc("/api/proxy/{name}/config", httppkg.MakeHTTPHandlerFunc(apiController.GetProxyConfig)).Methods(http.MethodGet)
	subRouter.HandleFunc("/api/visitor/{name}/config", httppkg.MakeHTTPHandlerFunc(apiController.GetVisitorConfig)).Methods(http.MethodGet)

	if svr.storeSource != nil {
		subRouter.HandleFunc("/api/store/proxies", httppkg.MakeHTTPHandlerFunc(apiController.ListStoreProxies)).Methods(http.MethodGet)
		subRouter.HandleFunc("/api/store/proxies", httppkg.MakeHTTPHandlerFunc(apiController.CreateStoreProxy)).Methods(http.MethodPost)
		subRouter.HandleFunc("/api/store/proxies/{name}", httppkg.MakeHTTPHandlerFunc(apiController.GetStoreProxy)).Methods(http.MethodGet)
		subRouter.HandleFunc("/api/store/proxies/{name}", httppkg.MakeHTTPHandlerFunc(apiController.UpdateStoreProxy)).Methods(http.MethodPut)
		subRouter.HandleFunc("/api/store/proxies/{name}", httppkg.MakeHTTPHandlerFunc(apiController.DeleteStoreProxy)).Methods(http.MethodDelete)
		subRouter.HandleFunc("/api/store/visitors", httppkg.MakeHTTPHandlerFunc(apiController.ListStoreVisitors)).Methods(http.MethodGet)
		subRouter.HandleFunc("/api/store/visitors", httppkg.MakeHTTPHandlerFunc(apiController.CreateStoreVisitor)).Methods(http.MethodPost)
		subRouter.HandleFunc("/api/store/visitors/{name}", httppkg.MakeHTTPHandlerFunc(apiController.GetStoreVisitor)).Methods(http.MethodGet)
		subRouter.HandleFunc("/api/store/visitors/{name}", httppkg.MakeHTTPHandlerFunc(apiController.UpdateStoreVisitor)).Methods(http.MethodPut)
		subRouter.HandleFunc("/api/store/visitors/{name}", httppkg.MakeHTTPHandlerFunc(apiController.DeleteStoreVisitor)).Methods(http.MethodDelete)
	}

	subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
	subRouter.PathPrefix("/static/").Handler(
		netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
	).Methods("GET")
	subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
	})
}

func healthz(w http.ResponseWriter, _ *http.Request) {
	w.WriteHeader(http.StatusOK)
}

func newAPIController(svr *Service) *adminapi.Controller {
	manager := newServiceConfigManager(svr)
	return adminapi.NewController(adminapi.ControllerParams{
		ServerAddr: svr.common.ServerAddr,
		Manager:    manager,
	})
}

// getAllProxyStatus returns all proxy statuses.
func (svr *Service) getAllProxyStatus() []*proxy.WorkingStatus {
	svr.ctlMu.RLock()
	ctl := svr.ctl
	svr.ctlMu.RUnlock()
	if ctl == nil {
		return nil
	}
	return ctl.pm.GetAllProxyStatus()
}


================================================
FILE: client/config_manager.go
================================================
package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/fatedier/frp/client/configmgmt"
	"github.com/fatedier/frp/client/proxy"
	"github.com/fatedier/frp/pkg/config"
	"github.com/fatedier/frp/pkg/config/source"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/config/v1/validation"
	"github.com/fatedier/frp/pkg/util/log"
)

type serviceConfigManager struct {
	svr *Service
}

func newServiceConfigManager(svr *Service) configmgmt.ConfigManager {
	return &serviceConfigManager{svr: svr}
}

func (m *serviceConfigManager) ReloadFromFile(strict bool) error {
	if m.svr.configFilePath == "" {
		return fmt.Errorf("%w: frpc has no config file path", configmgmt.ErrInvalidArgument)
	}

	result, err := config.LoadClientConfigResult(m.svr.configFilePath, strict)
	if err != nil {
		return fmt.Errorf("%w: %v", configmgmt.ErrInvalidArgument, err)
	}

	proxyCfgsForValidation, visitorCfgsForValidation := config.FilterClientConfigurers(
		result.Common,
		result.Proxies,
		result.Visitors,
	)
	proxyCfgsForValidation = config.CompleteProxyConfigurers(proxyCfgsForValidation)
	visitorCfgsForValidation = config.CompleteVisitorConfigurers(visitorCfgsForValidation)

	if _, err := validation.ValidateAllClientConfig(result.Common, proxyCfgsForValidation, visitorCfgsForValidation, m.svr.unsafeFeatures); err != nil {
		return fmt.Errorf("%w: %v", configmgmt.ErrInvalidArgument, err)
	}

	if err := m.svr.UpdateConfigSource(result.Common, result.Proxies, result.Visitors); err != nil {
		return fmt.Errorf("%w: %v", configmgmt.ErrApplyConfig, err)
	}

	log.Infof("success reload conf")
	return nil
}

func (m *serviceConfigManager) ReadConfigFile() (string, error) {
	if m.svr.configFilePath == "" {
		return "", fmt.Errorf("%w: frpc has no config file path", configmgmt.ErrInvalidArgument)
	}

	content, err := os.ReadFile(m.svr.configFilePath)
	if err != nil {
		return "", fmt.Errorf("%w: %v", configmgmt.ErrInvalidArgument, err)
	}
	return string(content), nil
}

func (m *serviceConfigManager) WriteConfigFile(content []byte) error {
	if len(content) == 0 {
		return fmt.Errorf("%w: body can't be empty", configmgmt.ErrInvalidArgument)
	}

	if err := os.WriteFile(m.svr.configFilePath, content, 0o600); err != nil {
		return err
	}
	return nil
}

func (m *serviceConfigManager) GetProxyStatus() []*proxy.WorkingStatus {
	return m.svr.getAllProxyStatus()
}

func (m *serviceConfigManager) GetProxyConfig(name string) (v1.ProxyConfigurer, bool) {
	// Try running proxy manager first
	ws, ok := m.svr.getProxyStatus(name)
	if ok {
		return ws.Cfg, true
	}

	// Fallback to store
	m.svr.reloadMu.Lock()
	storeSource := m.svr.storeSource
	m.svr.reloadMu.Unlock()

	if storeSource != nil {
		cfg := storeSource.GetProxy(name)
		if cfg != nil {
			return cfg, true
		}
	}
	return nil, false
}

func (m *serviceConfigManager) GetVisitorConfig(name string) (v1.VisitorConfigurer, bool) {
	// Try running visitor manager first
	cfg, ok := m.svr.getVisitorCfg(name)
	if ok {
		return cfg, true
	}

	// Fallback to store
	m.svr.reloadMu.Lock()
	storeSource := m.svr.storeSource
	m.svr.reloadMu.Unlock()

	if storeSource != nil {
		vcfg := storeSource.GetVisitor(name)
		if vcfg != nil {
			return vcfg, true
		}
	}
	return nil, false
}

func (m *serviceConfigManager) IsStoreProxyEnabled(name string) bool {
	if name == "" {
		return false
	}

	m.svr.reloadMu.Lock()
	storeSource := m.svr.storeSource
	m.svr.reloadMu.Unlock()

	if storeSource == nil {
		return false
	}

	cfg := storeSource.GetProxy(name)
	if cfg == nil {
		return false
	}
	enabled := cfg.GetBaseConfig().Enabled
	return enabled == nil || *enabled
}

func (m *serviceConfigManager) StoreEnabled() bool {
	m.svr.reloadMu.Lock()
	storeSource := m.svr.storeSource
	m.svr.reloadMu.Unlock()
	return storeSource != nil
}

func (m *serviceConfigManager) ListStoreProxies() ([]v1.ProxyConfigurer, error) {
	storeSource, err := m.storeSourceOrError()
	if err != nil {
		return nil, err
	}
	return storeSource.GetAllProxies()
}

func (m *serviceConfigManager) GetStoreProxy(name string) (v1.ProxyConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
	}

	storeSource, err := m.storeSourceOrError()
	if err != nil {
		return nil, err
	}

	cfg := storeSource.GetProxy(name)
	if cfg == nil {
		return nil, fmt.Errorf("%w: proxy %q", configmgmt.ErrNotFound, name)
	}
	return cfg, nil
}

func (m *serviceConfigManager) CreateStoreProxy(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
	if err := m.validateStoreProxyConfigurer(cfg); err != nil {
		return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
	}

	name := cfg.GetBaseConfig().Name
	persisted, err := m.withStoreProxyMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.AddProxy(cfg); err != nil {
			if errors.Is(err, source.ErrAlreadyExists) {
				return fmt.Errorf("%w: %v", configmgmt.ErrConflict, err)
			}
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	log.Infof("store: created proxy %q", name)
	return persisted, nil
}

func (m *serviceConfigManager) UpdateStoreProxy(name string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
	}
	if cfg == nil {
		return nil, fmt.Errorf("%w: invalid proxy config: type is required", configmgmt.ErrInvalidArgument)
	}
	bodyName := cfg.GetBaseConfig().Name
	if bodyName != name {
		return nil, fmt.Errorf("%w: proxy name in URL must match name in body", configmgmt.ErrInvalidArgument)
	}
	if err := m.validateStoreProxyConfigurer(cfg); err != nil {
		return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
	}

	persisted, err := m.withStoreProxyMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.UpdateProxy(cfg); err != nil {
			if errors.Is(err, source.ErrNotFound) {
				return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
			}
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	log.Infof("store: updated proxy %q", name)
	return persisted, nil
}

func (m *serviceConfigManager) DeleteStoreProxy(name string) error {
	if name == "" {
		return fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
	}

	if err := m.withStoreMutationAndReload(func(storeSource *source.StoreSource) error {
		if err := storeSource.RemoveProxy(name); err != nil {
			if errors.Is(err, source.ErrNotFound) {
				return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
			}
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	log.Infof("store: deleted proxy %q", name)
	return nil
}

func (m *serviceConfigManager) ListStoreVisitors() ([]v1.VisitorConfigurer, error) {
	storeSource, err := m.storeSourceOrError()
	if err != nil {
		return nil, err
	}
	return storeSource.GetAllVisitors()
}

func (m *serviceConfigManager) GetStoreVisitor(name string) (v1.VisitorConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
	}

	storeSource, err := m.storeSourceOrError()
	if err != nil {
		return nil, err
	}

	cfg := storeSource.GetVisitor(name)
	if cfg == nil {
		return nil, fmt.Errorf("%w: visitor %q", configmgmt.ErrNotFound, name)
	}
	return cfg, nil
}

func (m *serviceConfigManager) CreateStoreVisitor(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
	if err := m.validateStoreVisitorConfigurer(cfg); err != nil {
		return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
	}

	name := cfg.GetBaseConfig().Name
	persisted, err := m.withStoreVisitorMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.AddVisitor(cfg); err != nil {
			if errors.Is(err, source.ErrAlreadyExists) {
				return fmt.Errorf("%w: %v", configmgmt.ErrConflict, err)
			}
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	log.Infof("store: created visitor %q", name)
	return persisted, nil
}

func (m *serviceConfigManager) UpdateStoreVisitor(name string, cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
	}
	if cfg == nil {
		return nil, fmt.Errorf("%w: invalid visitor config: type is required", configmgmt.ErrInvalidArgument)
	}
	bodyName := cfg.GetBaseConfig().Name
	if bodyName != name {
		return nil, fmt.Errorf("%w: visitor name in URL must match name in body", configmgmt.ErrInvalidArgument)
	}
	if err := m.validateStoreVisitorConfigurer(cfg); err != nil {
		return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
	}

	persisted, err := m.withStoreVisitorMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.UpdateVisitor(cfg); err != nil {
			if errors.Is(err, source.ErrNotFound) {
				return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
			}
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	log.Infof("store: updated visitor %q", name)
	return persisted, nil
}

func (m *serviceConfigManager) DeleteStoreVisitor(name string) error {
	if name == "" {
		return fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
	}

	if err := m.withStoreMutationAndReload(func(storeSource *source.StoreSource) error {
		if err := storeSource.RemoveVisitor(name); err != nil {
			if errors.Is(err, source.ErrNotFound) {
				return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
			}
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	log.Infof("store: deleted visitor %q", name)
	return nil
}

func (m *serviceConfigManager) GracefulClose(d time.Duration) {
	m.svr.GracefulClose(d)
}

func (m *serviceConfigManager) storeSourceOrError() (*source.StoreSource, error) {
	m.svr.reloadMu.Lock()
	storeSource := m.svr.storeSource
	m.svr.reloadMu.Unlock()

	if storeSource == nil {
		return nil, fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
	}
	return storeSource, nil
}

func (m *serviceConfigManager) withStoreMutationAndReload(
	fn func(storeSource *source.StoreSource) error,
) error {
	m.svr.reloadMu.Lock()
	defer m.svr.reloadMu.Unlock()

	storeSource := m.svr.storeSource
	if storeSource == nil {
		return fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
	}

	if err := fn(storeSource); err != nil {
		return err
	}

	if err := m.svr.reloadConfigFromSourcesLocked(); err != nil {
		return fmt.Errorf("%w: failed to apply config: %v", configmgmt.ErrApplyConfig, err)
	}
	return nil
}

func (m *serviceConfigManager) withStoreProxyMutationAndReload(
	name string,
	fn func(storeSource *source.StoreSource) error,
) (v1.ProxyConfigurer, error) {
	m.svr.reloadMu.Lock()
	defer m.svr.reloadMu.Unlock()

	storeSource := m.svr.storeSource
	if storeSource == nil {
		return nil, fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
	}

	if err := fn(storeSource); err != nil {
		return nil, err
	}
	if err := m.svr.reloadConfigFromSourcesLocked(); err != nil {
		return nil, fmt.Errorf("%w: failed to apply config: %v", configmgmt.ErrApplyConfig, err)
	}

	persisted := storeSource.GetProxy(name)
	if persisted == nil {
		return nil, fmt.Errorf("%w: proxy %q not found in store after mutation", configmgmt.ErrApplyConfig, name)
	}
	return persisted.Clone(), nil
}

func (m *serviceConfigManager) withStoreVisitorMutationAndReload(
	name string,
	fn func(storeSource *source.StoreSource) error,
) (v1.VisitorConfigurer, error) {
	m.svr.reloadMu.Lock()
	defer m.svr.reloadMu.Unlock()

	storeSource := m.svr.storeSource
	if storeSource == nil {
		return nil, fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
	}

	if err := fn(storeSource); err != nil {
		return nil, err
	}
	if err := m.svr.reloadConfigFromSourcesLocked(); err != nil {
		return nil, fmt.Errorf("%w: failed to apply config: %v", configmgmt.ErrApplyConfig, err)
	}

	persisted := storeSource.GetVisitor(name)
	if persisted == nil {
		return nil, fmt.Errorf("%w: visitor %q not found in store after mutation", configmgmt.ErrApplyConfig, name)
	}
	return persisted.Clone(), nil
}

func (m *serviceConfigManager) validateStoreProxyConfigurer(cfg v1.ProxyConfigurer) error {
	if cfg == nil {
		return fmt.Errorf("invalid proxy config")
	}
	runtimeCfg := cfg.Clone()
	if runtimeCfg == nil {
		return fmt.Errorf("invalid proxy config")
	}
	runtimeCfg.Complete()
	return validation.ValidateProxyConfigurerForClient(runtimeCfg)
}

func (m *serviceConfigManager) validateStoreVisitorConfigurer(cfg v1.VisitorConfigurer) error {
	if cfg == nil {
		return fmt.Errorf("invalid visitor config")
	}
	runtimeCfg := cfg.Clone()
	if runtimeCfg == nil {
		return fmt.Errorf("invalid visitor config")
	}
	runtimeCfg.Complete()
	return validation.ValidateVisitorConfigurer(runtimeCfg)
}


================================================
FILE: client/config_manager_test.go
================================================
package client

import (
	"errors"
	"path/filepath"
	"testing"

	"github.com/fatedier/frp/client/configmgmt"
	"github.com/fatedier/frp/pkg/config/source"
	v1 "github.com/fatedier/frp/pkg/config/v1"
)

func newTestRawTCPProxyConfig(name string) *v1.TCPProxyConfig {
	return &v1.TCPProxyConfig{
		ProxyBaseConfig: v1.ProxyBaseConfig{
			Name: name,
			Type: "tcp",
			ProxyBackend: v1.ProxyBackend{
				LocalPort: 10080,
			},
		},
	}
}

func TestServiceConfigManagerCreateStoreProxyConflict(t *testing.T) {
	storeSource, err := source.NewStoreSource(source.StoreSourceConfig{
		Path: filepath.Join(t.TempDir(), "store.json"),
	})
	if err != nil {
		t.Fatalf("new store source: %v", err)
	}
	if err := storeSource.AddProxy(newTestRawTCPProxyConfig("p1")); err != nil {
		t.Fatalf("seed proxy: %v", err)
	}

	agg := source.NewAggregator(source.NewConfigSource())
	agg.SetStoreSource(storeSource)

	mgr := &serviceConfigManager{
		svr: &Service{
			aggregator:   agg,
			configSource: agg.ConfigSource(),
			storeSource:  storeSource,
			reloadCommon: &v1.ClientCommonConfig{},
		},
	}

	_, err = mgr.CreateStoreProxy(newTestRawTCPProxyConfig("p1"))
	if err == nil {
		t.Fatal("expected conflict error")
	}
	if !errors.Is(err, configmgmt.ErrConflict) {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestServiceConfigManagerCreateStoreProxyKeepsStoreOnReloadFailure(t *testing.T) {
	storeSource, err := source.NewStoreSource(source.StoreSourceConfig{
		Path: filepath.Join(t.TempDir(), "store.json"),
	})
	if err != nil {
		t.Fatalf("new store source: %v", err)
	}

	mgr := &serviceConfigManager{
		svr: &Service{
			storeSource:  storeSource,
			reloadCommon: &v1.ClientCommonConfig{},
		},
	}

	_, err = mgr.CreateStoreProxy(newTestRawTCPProxyConfig("p1"))
	if err == nil {
		t.Fatal("expected apply config error")
	}
	if !errors.Is(err, configmgmt.ErrApplyConfig) {
		t.Fatalf("unexpected error: %v", err)
	}
	if storeSource.GetProxy("p1") == nil {
		t.Fatal("proxy should remain in store after reload failure")
	}
}

func TestServiceConfigManagerCreateStoreProxyStoreDisabled(t *testing.T) {
	mgr := &serviceConfigManager{
		svr: &Service{
			reloadCommon: &v1.ClientCommonConfig{},
		},
	}

	_, err := mgr.CreateStoreProxy(newTestRawTCPProxyConfig("p1"))
	if err == nil {
		t.Fatal("expected store disabled error")
	}
	if !errors.Is(err, configmgmt.ErrStoreDisabled) {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestServiceConfigManagerCreateStoreProxyDoesNotPersistRuntimeDefaults(t *testing.T) {
	storeSource, err := source.NewStoreSource(source.StoreSourceConfig{
		Path: filepath.Join(t.TempDir(), "store.json"),
	})
	if err != nil {
		t.Fatalf("new store source: %v", err)
	}
	agg := source.NewAggregator(source.NewConfigSource())
	agg.SetStoreSource(storeSource)

	mgr := &serviceConfigManager{
		svr: &Service{
			aggregator:   agg,
			configSource: agg.ConfigSource(),
			storeSource:  storeSource,
			reloadCommon: &v1.ClientCommonConfig{},
		},
	}

	persisted, err := mgr.CreateStoreProxy(newTestRawTCPProxyConfig("raw-proxy"))
	if err != nil {
		t.Fatalf("create store proxy: %v", err)
	}
	if persisted == nil {
		t.Fatal("expected persisted proxy to be returned")
	}

	got := storeSource.GetProxy("raw-proxy")
	if got == nil {
		t.Fatal("proxy not found in store")
	}
	if got.GetBaseConfig().LocalIP != "" {
		t.Fatalf("localIP was persisted with runtime default: %q", got.GetBaseConfig().LocalIP)
	}
	if got.GetBaseConfig().Transport.BandwidthLimitMode != "" {
		t.Fatalf("bandwidthLimitMode was persisted with runtime default: %q", got.GetBaseConfig().Transport.BandwidthLimitMode)
	}
}


================================================
FILE: client/configmgmt/types.go
================================================
package configmgmt

import (
	"errors"
	"time"

	"github.com/fatedier/frp/client/proxy"
	v1 "github.com/fatedier/frp/pkg/config/v1"
)

var (
	ErrInvalidArgument = errors.New("invalid argument")
	ErrNotFound        = errors.New("not found")
	ErrConflict        = errors.New("conflict")
	ErrStoreDisabled   = errors.New("store disabled")
	ErrApplyConfig     = errors.New("apply config failed")
)

type ConfigManager interface {
	ReloadFromFile(strict bool) error

	ReadConfigFile() (string, error)
	WriteConfigFile(content []byte) error

	GetProxyStatus() []*proxy.WorkingStatus
	IsStoreProxyEnabled(name string) bool
	StoreEnabled() bool

	GetProxyConfig(name string) (v1.ProxyConfigurer, bool)
	GetVisitorConfig(name string) (v1.VisitorConfigurer, bool)

	ListStoreProxies() ([]v1.ProxyConfigurer, error)
	GetStoreProxy(name string) (v1.ProxyConfigurer, error)
	CreateStoreProxy(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error)
	UpdateStoreProxy(name string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error)
	DeleteStoreProxy(name string) error

	ListStoreVisitors() ([]v1.VisitorConfigurer, error)
	GetStoreVisitor(name string) (v1.VisitorConfigurer, error)
	CreateStoreVisitor(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error)
	UpdateStoreVisitor(name string, cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error)
	DeleteStoreVisitor(name string) error

	GracefulClose(d time.Duration)
}


================================================
FILE: client/connector.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"context"
	"crypto/tls"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"

	libnet "github.com/fatedier/golib/net"
	fmux "github.com/hashicorp/yamux"
	quic "github.com/quic-go/quic-go"
	"github.com/samber/lo"

	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/transport"
	netpkg "github.com/fatedier/frp/pkg/util/net"
	"github.com/fatedier/frp/pkg/util/xlog"
)

// Connector is an interface for establishing connections to the server.
type Connector interface {
	Open() error
	Connect() (net.Conn, error)
	Close() error
}

// defaultConnectorImpl is the default implementation of Connector for normal frpc.
type defaultConnectorImpl struct {
	ctx context.Context
	cfg *v1.ClientCommonConfig

	muxSession *fmux.Session
	quicConn   *quic.Conn
	closeOnce  sync.Once
}

func NewConnector(ctx context.Context, cfg *v1.ClientCommonConfig) Connector {
	return &defaultConnectorImpl{
		ctx: ctx,
		cfg: cfg,
	}
}

// Open opens an underlying connection to the server.
// The underlying connection is either a TCP connection or a QUIC connection.
// After the underlying connection is established, you can call Connect() to get a stream.
// If TCPMux isn't enabled, the underlying connection is nil, you will get a new real TCP connection every time you call Connect().
func (c *defaultConnectorImpl) Open() error {
	xl := xlog.FromContextSafe(c.ctx)

	// special for quic
	if strings.EqualFold(c.cfg.Transport.Protocol, "quic") {
		var tlsConfig *tls.Config
		var err error
		sn := c.cfg.Transport.TLS.ServerName
		if sn == "" {
			sn = c.cfg.ServerAddr
		}
		if lo.FromPtr(c.cfg.Transport.TLS.Enable) {
			tlsConfig, err = transport.NewClientTLSConfig(
				c.cfg.Transport.TLS.CertFile,
				c.cfg.Transport.TLS.KeyFile,
				c.cfg.Transport.TLS.TrustedCaFile,
				sn)
		} else {
			tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
		}
		if err != nil {
			xl.Warnf("fail to build tls configuration, err: %v", err)
			return err
		}
		tlsConfig.NextProtos = []string{"frp"}

		conn, err := quic.DialAddr(
			c.ctx,
			net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
			tlsConfig, &quic.Config{
				MaxIdleTimeout:     time.Duration(c.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
				MaxIncomingStreams: int64(c.cfg.Transport.QUIC.MaxIncomingStreams),
				KeepAlivePeriod:    time.Duration(c.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
			})
		if err != nil {
			return err
		}
		c.quicConn = conn
		return nil
	}

	if !lo.FromPtr(c.cfg.Transport.TCPMux) {
		return nil
	}

	conn, err := c.realConnect()
	if err != nil {
		return err
	}

	fmuxCfg := fmux.DefaultConfig()
	fmuxCfg.KeepAliveInterval = time.Duration(c.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
	// Use trace level for yamux logs
	fmuxCfg.LogOutput = xlog.NewTraceWriter(xl)
	fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
	session, err := fmux.Client(conn, fmuxCfg)
	if err != nil {
		return err
	}
	c.muxSession = session
	return nil
}

// Connect returns a stream from the underlying connection, or a new TCP connection if TCPMux isn't enabled.
func (c *defaultConnectorImpl) Connect() (net.Conn, error) {
	if c.quicConn != nil {
		stream, err := c.quicConn.OpenStreamSync(context.Background())
		if err != nil {
			return nil, err
		}
		return netpkg.QuicStreamToNetConn(stream, c.quicConn), nil
	} else if c.muxSession != nil {
		stream, err := c.muxSession.OpenStream()
		if err != nil {
			return nil, err
		}
		return stream, nil
	}

	return c.realConnect()
}

func (c *defaultConnectorImpl) realConnect() (net.Conn, error) {
	xl := xlog.FromContextSafe(c.ctx)
	var tlsConfig *tls.Config
	var err error
	tlsEnable := lo.FromPtr(c.cfg.Transport.TLS.Enable)
	if c.cfg.Transport.Protocol == "wss" {
		tlsEnable = true
	}
	if tlsEnable {
		sn := c.cfg.Transport.TLS.ServerName
		if sn == "" {
			sn = c.cfg.ServerAddr
		}

		tlsConfig, err = transport.NewClientTLSConfig(
			c.cfg.Transport.TLS.CertFile,
			c.cfg.Transport.TLS.KeyFile,
			c.cfg.Transport.TLS.TrustedCaFile,
			sn)
		if err != nil {
			xl.Warnf("fail to build tls configuration, err: %v", err)
			return nil, err
		}
	}

	proxyType, addr, auth, err := libnet.ParseProxyURL(c.cfg.Transport.ProxyURL)
	if err != nil {
		xl.Errorf("fail to parse proxy url")
		return nil, err
	}
	dialOptions := []libnet.DialOption{}
	protocol := c.cfg.Transport.Protocol
	switch protocol {
	case "websocket":
		protocol = "tcp"
		dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, "")}))
		dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{
			Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
		}))
		dialOptions = append(dialOptions, libnet.WithTLSConfig(tlsConfig))
	case "wss":
		protocol = "tcp"
		dialOptions = append(dialOptions, libnet.WithTLSConfigAndPriority(100, tlsConfig))
		// Make sure that if it is wss, the websocket hook is executed after the tls hook.
		dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
	default:
		dialOptions = append(dialOptions, libnet.WithAfterHook(libnet.AfterHook{
			Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
		}))
		dialOptions = append(dialOptions, libnet.WithTLSConfig(tlsConfig))
	}

	if c.cfg.Transport.ConnectServerLocalIP != "" {
		dialOptions = append(dialOptions, libnet.WithLocalAddr(c.cfg.Transport.ConnectServerLocalIP))
	}
	dialOptions = append(dialOptions,
		libnet.WithProtocol(protocol),
		libnet.WithTimeout(time.Duration(c.cfg.Transport.DialServerTimeout)*time.Second),
		libnet.WithKeepAlive(time.Duration(c.cfg.Transport.DialServerKeepAlive)*time.Second),
		libnet.WithProxy(proxyType, addr),
		libnet.WithProxyAuth(auth),
	)
	conn, err := libnet.DialContext(
		c.ctx,
		net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
		dialOptions...,
	)
	return conn, err
}

func (c *defaultConnectorImpl) Close() error {
	c.closeOnce.Do(func() {
		if c.quicConn != nil {
			_ = c.quicConn.CloseWithError(0, "")
		}
		if c.muxSession != nil {
			_ = c.muxSession.Close()
		}
	})
	return nil
}


================================================
FILE: client/control.go
================================================
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"context"
	"net"
	"sync/atomic"
	"time"

	"github.com/fatedier/frp/client/proxy"
	"github.com/fatedier/frp/client/visitor"
	"github.com/fatedier/frp/pkg/auth"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/naming"
	"github.com/fatedier/frp/pkg/transport"
	netpkg "github.com/fatedier/frp/pkg/util/net"
	"github.com/fatedier/frp/pkg/util/wait"
	"github.com/fatedier/frp/pkg/util/xlog"
	"github.com/fatedier/frp/pkg/vnet"
)

type SessionContext struct {
	// The client common configuration.
	Common *v1.ClientCommonConfig

	// Unique ID obtained from frps.
	// It should be attached to the login message when reconnecting.
	RunID string
	// Underlying control connection. Once conn is closed, the msgDispatcher and the entire Control will exit.
	Conn net.Conn
	// Indicates whether the connection is encrypted.
	ConnEncrypted bool
	// Auth runtime used for login, heartbeats, and encryption.
	Auth *auth.ClientAuth
	// Connector is used to create new connections, which could be real TCP connections or virtual streams.
	Connector Connector
	// Virtual net controller
	VnetController *vnet.Controller
}

type Control struct {
	// service context
	ctx context.Context
	xl  *xlog.Logger

	// session context
	sessionCtx *SessionContext

	// manage all proxies
	pm *proxy.Manager

	// manage all visitors
	vm *visitor.Manager

	doneCh chan struct{}

	// of time.Time, last time got the Pong message
	lastPong atomic.Value

	// The role of msgTransporter is similar to HTTP2.
	// It allows multiple messages to be sent simultaneously on the same control connection.
	// The server's response messages will be dispatched to the corresponding waiting goroutines based on the laneKey and message type.
	msgTransporter transport.MessageTransporter

	// msgDispatcher is a wrapper for control connection.
	// It provides a channel for sending messages, and you can register handlers to process messages based on their respective types.
	msgDispatcher *msg.Dispatcher
}

func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, error) {
	// new xlog instance
	ctl := &Control{
		ctx:        ctx,
		xl:         xlog.FromContextSafe(ctx),
		sessionCtx: sessionCtx,
		doneCh:     make(chan struct{}),
	}
	ctl.lastPong.Store(time.Now())

	if sessionCtx.ConnEncrypted {
		cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, sessionCtx.Auth.EncryptionKey())
		if err != nil {
			return nil, err
		}
		ctl.msgDispatcher = msg.NewDispatcher(cryptoRW)
	} else {
		ctl.msgDispatcher = msg.NewDispatcher(sessionCtx.Conn)
	}
	ctl.registerMsgHandlers()
	ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher)

	ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, sessionCtx.Auth.EncryptionKey(), ctl.msgTransporter, sessionCtx.VnetController)
	ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
		ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
	return ctl, nil
}

func (ctl *Control) Run(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) {
	go ctl.worker()

	// start all proxies
	ctl.pm.UpdateAll(proxyCfgs)

	// start all visitors
	ctl.vm.UpdateAll(visitorCfgs)
}

func (ctl *Control) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
	ctl.pm.SetInWorkConnCallback(cb)
}

func (ctl *Control) handleReqWorkConn(_ msg.Message) {
	xl := ctl.xl
	workConn, err := ctl.connectServer()
	if err != nil {
		xl.Warnf("start new connection to server error: %v", err)
		return
	}

	m := &msg.NewWorkConn{
		RunID: ctl.sessionCtx.RunID,
	}
	if err = ctl.sessionCtx.Auth.Setter.SetNewWorkConn(m); err != nil {
		xl.Warnf("error during NewWorkConn authentication: %v", err)
		workConn.Close()
		return
	}
	if err = msg.WriteMsg(workConn, m); err != nil {
		xl.Warnf("work connection write to server error: %v", err)
		workConn.Close()
		return
	}

	var startMsg msg.StartWorkConn
	if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
		xl.Tracef("work connection closed before response StartWorkConn message: %v", err)
		workConn.Close()
		return
	}
	if startMsg.Error != "" {
		xl.Errorf("StartWorkConn contains error: %s", startMsg.Error)
		workConn.Close()
		return
	}

	startMsg.ProxyName = naming.StripUserPrefix(ctl.sessionCtx.Common.User, startMsg.ProxyName)

	// dispatch this work connection to related proxy
	ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
}

func (ctl *Control) handleNewProxyResp(m msg.Message) {
	xl := ctl.xl
	inMsg := m.(*msg.NewProxyResp)
	// Server will return NewProxyResp message to each NewProxy message.
	// Start a new proxy handler if no error got
	proxyName := naming.StripUserPrefix(ctl.sessionCtx.Common.User, inMsg.ProxyName)
	err := ctl.pm.StartProxy(proxyName, inMsg.RemoteAddr, inMsg.Error)
	if err != nil {
		xl.Warnf("[%s] start error: %v", proxyName, err)
	} else {
		xl.Infof("[%s] start proxy success", proxyName)
	}
}

func (ctl *Control) handleNatHoleResp(m msg.Message) {
	xl := ctl.xl
	inMsg := m.(*msg.NatHoleResp)

	// Dispatch the NatHoleResp message to the related proxy.
	ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
	if !ok {
		xl.Tracef("dispatch NatHoleResp message to related proxy error")
	}
}

func (ctl *Control) handlePong(m msg.Message) {
	xl := ctl.xl
	inMsg := m.(*msg.Pong)

	if inMsg.Error != "" {
		xl.Errorf("pong message contains error: %s", inMsg.Error)
		ctl.closeSession()
		return
	}
	ctl.lastPong.Store(time.Now())
	xl.Debugf("receive heartbeat from server")
}

// closeSession closes the control connection.
func (ctl *Control) closeSession() {
	ctl.sessionCtx.Conn.Close()
	ctl.sessionCtx.Connector.Close()
}

func (ctl *Control) Close() error {
	return ctl.GracefulClose(0)
}

func (ctl *Control) GracefulClose(d time.Duration) error {
	ctl.pm.Close()
	ctl.vm.Close()

	time.Sleep(d)

	ctl.closeSession()
	return nil
}

// Done returns a channel that will be closed after all resources are released
func (ctl *Control) Done() <-chan struct{} {
	return ctl.doneCh
}

// connectServer return a new connection to frps
func (ctl *Control) connectServer() (net.Conn, error) {
	return ctl.sessionCtx.Connector.Connect()
}

func (ctl *Control) registerMsgHandlers() {
	ctl.msgDispatcher.RegisterHandler(&msg.ReqWorkConn{}, msg.AsyncHandler(ctl.handleReqWorkConn))
	ctl.msgDispatcher.RegisterHandler(&msg.NewProxyResp{}, ctl.handleNewProxyResp)
	ctl.msgDispatcher.RegisterHandler(&msg.NatHoleResp{}, ctl.handleNatHoleResp)
	ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
}

// heartbeatWorker sends heartbeat to server and check heartbeat timeout.
func (ctl *Control) heartbeatWorker() {
	xl := ctl.xl

	if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
		// Send heartbeat to server.
		sendHeartBeat := func() (bool, error) {
			xl.Debugf("send heartbeat to server")
			pingMsg := &msg.Ping{}
			if err := ctl.sessionCtx.Auth.Setter.SetPing(pingMsg); err != nil {
				xl.Warnf("error during ping authentication: %v, skip sending ping message", err)
				return false, err
			}
			_ = ctl.msgDispatcher.Send(pingMsg)
			return false, nil
		}

		go wait.BackoffUntil(sendHeartBeat,
			wait.NewFastBackoffManager(wait.FastBackoffOptions{
				Duration:           time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second,
				InitDurationIfFail: time.Second,
				Factor:             2.0,
				Jitter:             0.1,
				MaxDuration:        time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second,
			}),
			true, ctl.doneCh,
		)
	}

	// Check heartbeat timeout.
	if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 {
		go wait.Until(func() {
			if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
				xl.Warnf("heartbeat timeout")
				ctl.closeSession()
				return
			}
		}, time.Second, ctl.doneCh)
	}
}

func (ctl *Control) worker() {
	xl := ctl.xl
	go ctl.heartbeatWorker()
	go ctl.msgDispatcher.Run()

	<-ctl.msgDispatcher.Done()
	xl.Debugf("control message dispatcher exited")
	ctl.closeSession()

	ctl.pm.Close()
	ctl.vm.Close()
	close(ctl.doneCh)
}

func (ctl *Control) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
	ctl.vm.UpdateAll(visitorCfgs)
	ctl.pm.UpdateAll(proxyCfgs)
	return nil
}


================================================
FILE: client/event/event.go
================================================
package event

import (
	"errors"

	"github.com/fatedier/frp/pkg/msg"
)

var ErrPayloadType = errors.New("error payload type")

type Handler func(payload any) error

type StartProxyPayload struct {
	NewProxyMsg *msg.NewProxy
}

type CloseProxyPayload struct {
	CloseProxyMsg *msg.CloseProxy
}


================================================
FILE: client/health/health.go
================================================
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package health

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"
	"time"

	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/util/xlog"
)

var ErrHealthCheckType = errors.New("error health check type")

type Monitor struct {
	checkType      string
	interval       time.Duration
	timeout        time.Duration
	maxFailedTimes int

	// For tcp
	addr string

	// For http
	url            string
	header         http.Header
	failedTimes    uint64
	statusOK       bool
	statusNormalFn func()
	statusFailedFn func()

	ctx    context.Context
	cancel context.CancelFunc
}

func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
	statusNormalFn func(), statusFailedFn func(),
) *Monitor {
	if cfg.IntervalSeconds <= 0 {
		cfg.IntervalSeconds = 10
	}
	if cfg.TimeoutSeconds <= 0 {
		cfg.TimeoutSeconds = 3
	}
	if cfg.MaxFailed <= 0 {
		cfg.MaxFailed = 1
	}
	newctx, cancel := context.WithCancel(ctx)

	var url string
	if cfg.Type == "http" && cfg.Path != "" {
		s := "http://" + addr
		if !strings.HasPrefix(cfg.Path, "/") {
			s += "/"
		}
		url = s + cfg.Path
	}
	header := make(http.Header)
	for _, h := range cfg.HTTPHeaders {
		header.Set(h.Name, h.Value)
	}

	return &Monitor{
		checkType:      cfg.Type,
		interval:       time.Duration(cfg.IntervalSeconds) * time.Second,
		timeout:        time.Duration(cfg.TimeoutSeconds) * time.Second,
		maxFailedTimes: cfg.MaxFailed,
		addr:           addr,
		url:            url,
		header:         header,
		statusOK:       false,
		statusNormalFn: statusNormalFn,
		statusFailedFn: statusFailedFn,
		ctx:            newctx,
		cancel:         cancel,
	}
}

func (monitor *Monitor) Start() {
	go monitor.checkWorker()
}

func (monitor *Monitor) Stop() {
	monitor.cancel()
}

func (monitor *Monitor) checkWorker() {
	xl := xlog.FromContextSafe(monitor.ctx)
	for {
		doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
		err := monitor.doCheck(doCtx)

		// check if this monitor has been closed
		select {
		case <-monitor.ctx.Done():
			cancel()
			return
		default:
			cancel()
		}

		if err == nil {
			xl.Tracef("do one health check success")
			if !monitor.statusOK && monitor.statusNormalFn != nil {
				xl.Infof("health check status change to success")
				monitor.statusOK = true
				monitor.statusNormalFn()
			}
		} else {
			xl.Warnf("do one health check failed: %v", err)
			monitor.failedTimes++
			if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
				xl.Warnf("health check status change to failed")
				monitor.statusOK = false
				monitor.statusFailedFn()
			}
		}

		time.Sleep(monitor.interval)
	}
}

func (monitor *Monitor) doCheck(ctx context.Context) error {
	switch monitor.checkType {
	case "tcp":
		return monitor.doTCPCheck(ctx)
	case "http":
		return monitor.doHTTPCheck(ctx)
	default:
		return ErrHealthCheckType
	}
}

func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
	// if tcp address is not specified, always return nil
	if monitor.addr == "" {
		return nil
	}

	var d net.Dialer
	conn, err := d.DialContext(ctx, "tcp", monitor.addr)
	if err != nil {
		return err
	}
	conn.Close()
	return nil
}

func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
	req, err := http.NewRequestWithContext(ctx, "GET", monitor.url, nil)
	if err != nil {
		return err
	}
	req.Header = monitor.header
	req.Host = monitor.header.Get("Host")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	_, _ = io.Copy(io.Discard, resp.Body)

	if resp.StatusCode/100 != 2 {
		return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
	}
	return nil
}


================================================
FILE: client/http/controller.go
================================================
// Copyright 2025 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package http

import (
	"cmp"
	"errors"
	"fmt"
	"net"
	"net/http"
	"slices"
	"strconv"
	"time"

	"github.com/fatedier/frp/client/configmgmt"
	"github.com/fatedier/frp/client/http/model"
	"github.com/fatedier/frp/client/proxy"
	httppkg "github.com/fatedier/frp/pkg/util/http"
	"github.com/fatedier/frp/pkg/util/jsonx"
)

// Controller handles HTTP API requests for frpc.
type Controller struct {
	serverAddr string
	manager    configmgmt.ConfigManager
}

// ControllerParams contains parameters for creating an APIController.
type ControllerParams struct {
	ServerAddr string
	Manager    configmgmt.ConfigManager
}

func NewController(params ControllerParams) *Controller {
	return &Controller{
		serverAddr: params.ServerAddr,
		manager:    params.Manager,
	}
}

func (c *Controller) toHTTPError(err error) error {
	if err == nil {
		return nil
	}

	code := http.StatusInternalServerError
	switch {
	case errors.Is(err, configmgmt.ErrInvalidArgument):
		code = http.StatusBadRequest
	case errors.Is(err, configmgmt.ErrNotFound), errors.Is(err, configmgmt.ErrStoreDisabled):
		code = http.StatusNotFound
	case errors.Is(err, configmgmt.ErrConflict):
		code = http.StatusConflict
	}
	return httppkg.NewError(code, err.Error())
}

// Reload handles GET /api/reload
func (c *Controller) Reload(ctx *httppkg.Context) (any, error) {
	strictConfigMode := false
	strictStr := ctx.Query("strictConfig")
	if strictStr != "" {
		strictConfigMode, _ = strconv.ParseBool(strictStr)
	}

	if err := c.manager.ReloadFromFile(strictConfigMode); err != nil {
		return nil, c.toHTTPError(err)
	}
	return nil, nil
}

// Stop handles POST /api/stop
func (c *Controller) Stop(ctx *httppkg.Context) (any, error) {
	go c.manager.GracefulClose(100 * time.Millisecond)
	return nil, nil
}

// Status handles GET /api/status
func (c *Controller) Status(ctx *httppkg.Context) (any, error) {
	res := make(model.StatusResp)
	ps := c.manager.GetProxyStatus()
	if ps == nil {
		return res, nil
	}

	for _, status := range ps {
		res[status.Type] = append(res[status.Type], c.buildProxyStatusResp(status))
	}

	for _, arrs := range res {
		if len(arrs) <= 1 {
			continue
		}
		slices.SortFunc(arrs, func(a, b model.ProxyStatusResp) int {
			return cmp.Compare(a.Name, b.Name)
		})
	}
	return res, nil
}

// GetConfig handles GET /api/config
func (c *Controller) GetConfig(ctx *httppkg.Context) (any, error) {
	content, err := c.manager.ReadConfigFile()
	if err != nil {
		return nil, c.toHTTPError(err)
	}
	return content, nil
}

// PutConfig handles PUT /api/config
func (c *Controller) PutConfig(ctx *httppkg.Context) (any, error) {
	body, err := ctx.Body()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("read request body error: %v", err))
	}

	if len(body) == 0 {
		return nil, httppkg.NewError(http.StatusBadRequest, "body can't be empty")
	}

	if err := c.manager.WriteConfigFile(body); err != nil {
		return nil, c.toHTTPError(err)
	}
	return nil, nil
}

func (c *Controller) buildProxyStatusResp(status *proxy.WorkingStatus) model.ProxyStatusResp {
	psr := model.ProxyStatusResp{
		Name:   status.Name,
		Type:   status.Type,
		Status: status.Phase,
		Err:    status.Err,
	}
	baseCfg := status.Cfg.GetBaseConfig()
	if baseCfg.LocalPort != 0 {
		psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
	}
	psr.Plugin = baseCfg.Plugin.Type

	if status.Err == "" {
		psr.RemoteAddr = status.RemoteAddr
		if slices.Contains([]string{"tcp", "udp"}, status.Type) {
			psr.RemoteAddr = c.serverAddr + psr.RemoteAddr
		}
	}

	if c.manager.IsStoreProxyEnabled(status.Name) {
		psr.Source = model.SourceStore
	}
	return psr
}

// GetProxyConfig handles GET /api/proxy/{name}/config
func (c *Controller) GetProxyConfig(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "proxy name is required")
	}

	cfg, ok := c.manager.GetProxyConfig(name)
	if !ok {
		return nil, httppkg.NewError(http.StatusNotFound, fmt.Sprintf("proxy %q not found", name))
	}

	payload, err := model.ProxyDefinitionFromConfigurer(cfg)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}
	return payload, nil
}

// GetVisitorConfig handles GET /api/visitor/{name}/config
func (c *Controller) GetVisitorConfig(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "visitor name is required")
	}

	cfg, ok := c.manager.GetVisitorConfig(name)
	if !ok {
		return nil, httppkg.NewError(http.StatusNotFound, fmt.Sprintf("visitor %q not found", name))
	}

	payload, err := model.VisitorDefinitionFromConfigurer(cfg)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}
	return payload, nil
}

func (c *Controller) ListStoreProxies(ctx *httppkg.Context) (any, error) {
	proxies, err := c.manager.ListStoreProxies()
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	resp := model.ProxyListResp{Proxies: make([]model.ProxyDefinition, 0, len(proxies))}
	for _, p := range proxies {
		payload, err := model.ProxyDefinitionFromConfigurer(p)
		if err != nil {
			return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
		}
		resp.Proxies = append(resp.Proxies, payload)
	}
	slices.SortFunc(resp.Proxies, func(a, b model.ProxyDefinition) int {
		return cmp.Compare(a.Name, b.Name)
	})
	return resp, nil
}

func (c *Controller) GetStoreProxy(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "proxy name is required")
	}

	p, err := c.manager.GetStoreProxy(name)
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	payload, err := model.ProxyDefinitionFromConfigurer(p)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}

	return payload, nil
}

func (c *Controller) CreateStoreProxy(ctx *httppkg.Context) (any, error) {
	body, err := ctx.Body()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("read body error: %v", err))
	}

	var payload model.ProxyDefinition
	if err := jsonx.Unmarshal(body, &payload); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("parse JSON error: %v", err))
	}

	if err := payload.Validate("", false); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	cfg, err := payload.ToConfigurer()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	created, err := c.manager.CreateStoreProxy(cfg)
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	resp, err := model.ProxyDefinitionFromConfigurer(created)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}
	return resp, nil
}

func (c *Controller) UpdateStoreProxy(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "proxy name is required")
	}

	body, err := ctx.Body()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("read body error: %v", err))
	}

	var payload model.ProxyDefinition
	if err := jsonx.Unmarshal(body, &payload); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("parse JSON error: %v", err))
	}

	if err := payload.Validate(name, true); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	cfg, err := payload.ToConfigurer()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	updated, err := c.manager.UpdateStoreProxy(name, cfg)
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	resp, err := model.ProxyDefinitionFromConfigurer(updated)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}
	return resp, nil
}

func (c *Controller) DeleteStoreProxy(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "proxy name is required")
	}

	if err := c.manager.DeleteStoreProxy(name); err != nil {
		return nil, c.toHTTPError(err)
	}
	return nil, nil
}

func (c *Controller) ListStoreVisitors(ctx *httppkg.Context) (any, error) {
	visitors, err := c.manager.ListStoreVisitors()
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	resp := model.VisitorListResp{Visitors: make([]model.VisitorDefinition, 0, len(visitors))}
	for _, v := range visitors {
		payload, err := model.VisitorDefinitionFromConfigurer(v)
		if err != nil {
			return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
		}
		resp.Visitors = append(resp.Visitors, payload)
	}
	slices.SortFunc(resp.Visitors, func(a, b model.VisitorDefinition) int {
		return cmp.Compare(a.Name, b.Name)
	})
	return resp, nil
}

func (c *Controller) GetStoreVisitor(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "visitor name is required")
	}

	v, err := c.manager.GetStoreVisitor(name)
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	payload, err := model.VisitorDefinitionFromConfigurer(v)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}

	return payload, nil
}

func (c *Controller) CreateStoreVisitor(ctx *httppkg.Context) (any, error) {
	body, err := ctx.Body()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("read body error: %v", err))
	}

	var payload model.VisitorDefinition
	if err := jsonx.Unmarshal(body, &payload); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("parse JSON error: %v", err))
	}

	if err := payload.Validate("", false); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	cfg, err := payload.ToConfigurer()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	created, err := c.manager.CreateStoreVisitor(cfg)
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	resp, err := model.VisitorDefinitionFromConfigurer(created)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}
	return resp, nil
}

func (c *Controller) UpdateStoreVisitor(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "visitor name is required")
	}

	body, err := ctx.Body()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("read body error: %v", err))
	}

	var payload model.VisitorDefinition
	if err := jsonx.Unmarshal(body, &payload); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("parse JSON error: %v", err))
	}

	if err := payload.Validate(name, true); err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	cfg, err := payload.ToConfigurer()
	if err != nil {
		return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
	}
	updated, err := c.manager.UpdateStoreVisitor(name, cfg)
	if err != nil {
		return nil, c.toHTTPError(err)
	}

	resp, err := model.VisitorDefinitionFromConfigurer(updated)
	if err != nil {
		return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
	}
	return resp, nil
}

func (c *Controller) DeleteStoreVisitor(ctx *httppkg.Context) (any, error) {
	name := ctx.Param("name")
	if name == "" {
		return nil, httppkg.NewError(http.StatusBadRequest, "visitor name is required")
	}

	if err := c.manager.DeleteStoreVisitor(name); err != nil {
		return nil, c.toHTTPError(err)
	}
	return nil, nil
}


================================================
FILE: client/http/controller_test.go
================================================
package http

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gorilla/mux"

	"github.com/fatedier/frp/client/configmgmt"
	"github.com/fatedier/frp/client/http/model"
	"github.com/fatedier/frp/client/proxy"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	httppkg "github.com/fatedier/frp/pkg/util/http"
)

type fakeConfigManager struct {
	reloadFromFileFn      func(strict bool) error
	readConfigFileFn      func() (string, error)
	writeConfigFileFn     func(content []byte) error
	getProxyStatusFn      func() []*proxy.WorkingStatus
	isStoreProxyEnabledFn func(name string) bool
	storeEnabledFn        func() bool
	getProxyConfigFn      func(name string) (v1.ProxyConfigurer, bool)
	getVisitorConfigFn    func(name string) (v1.VisitorConfigurer, bool)

	listStoreProxiesFn  func() ([]v1.ProxyConfigurer, error)
	getStoreProxyFn     func(name string) (v1.ProxyConfigurer, error)
	createStoreProxyFn  func(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error)
	updateStoreProxyFn  func(name string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error)
	deleteStoreProxyFn  func(name string) error
	listStoreVisitorsFn func() ([]v1.VisitorConfigurer, error)
	getStoreVisitorFn   func(name string) (v1.VisitorConfigurer, error)
	createStoreVisitFn  func(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error)
	updateStoreVisitFn  func(name string, cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error)
	deleteStoreVisitFn  func(name string) error
	gracefulCloseFn     func(d time.Duration)
}

func (m *fakeConfigManager) ReloadFromFile(strict bool) error {
	if m.reloadFromFileFn != nil {
		return m.reloadFromFileFn(strict)
	}
	return nil
}

func (m *fakeConfigManager) ReadConfigFile() (string, error) {
	if m.readConfigFileFn != nil {
		return m.readConfigFileFn()
	}
	return "", nil
}

func (m *fakeConfigManager) WriteConfigFile(content []byte) error {
	if m.writeConfigFileFn != nil {
		return m.writeConfigFileFn(content)
	}
	return nil
}

func (m *fakeConfigManager) GetProxyStatus() []*proxy.WorkingStatus {
	if m.getProxyStatusFn != nil {
		return m.getProxyStatusFn()
	}
	return nil
}

func (m *fakeConfigManager) IsStoreProxyEnabled(name string) bool {
	if m.isStoreProxyEnabledFn != nil {
		return m.isStoreProxyEnabledFn(name)
	}
	return false
}

func (m *fakeConfigManager) StoreEnabled() bool {
	if m.storeEnabledFn != nil {
		return m.storeEnabledFn()
	}
	return false
}

func (m *fakeConfigManager) GetProxyConfig(name string) (v1.ProxyConfigurer, bool) {
	if m.getProxyConfigFn != nil {
		return m.getProxyConfigFn(name)
	}
	return nil, false
}

func (m *fakeConfigManager) GetVisitorConfig(name string) (v1.VisitorConfigurer, bool) {
	if m.getVisitorConfigFn != nil {
		return m.getVisitorConfigFn(name)
	}
	return nil, false
}

func (m *fakeConfigManager) ListStoreProxies() ([]v1.ProxyConfigurer, error) {
	if m.listStoreProxiesFn != nil {
		return m.listStoreProxiesFn()
	}
	return nil, nil
}

func (m *fakeConfigManager) GetStoreProxy(name string) (v1.ProxyConfigurer, error) {
	if m.getStoreProxyFn != nil {
		return m.getStoreProxyFn(name)
	}
	return nil, nil
}

func (m *fakeConfigManager) CreateStoreProxy(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
	if m.createStoreProxyFn != nil {
		return m.createStoreProxyFn(cfg)
	}
	return cfg, nil
}

func (m *fakeConfigManager) UpdateStoreProxy(name string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
	if m.updateStoreProxyFn != nil {
		return m.updateStoreProxyFn(name, cfg)
	}
	return cfg, nil
}

func (m *fakeConfigManager) DeleteStoreProxy(name string) error {
	if m.deleteStoreProxyFn != nil {
		return m.deleteStoreProxyFn(name)
	}
	return nil
}

func (m *fakeConfigManager) ListStoreVisitors() ([]v1.VisitorConfigurer, error) {
	if m.listStoreVisitorsFn != nil {
		return m.listStoreVisitorsFn()
	}
	return nil, nil
}

func (m *fakeConfigManager) GetStoreVisitor(name string) (v1.VisitorConfigurer, error) {
	if m.getStoreVisitorFn != nil {
		return m.getStoreVisitorFn(name)
	}
	return nil, nil
}

func (m *fakeConfigManager) CreateStoreVisitor(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
	if m.createStoreVisitFn != nil {
		return m.createStoreVisitFn(cfg)
	}
	return cfg, nil
}

func (m *fakeConfigManager) UpdateStoreVisitor(name string, cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
	if m.updateStoreVisitFn != nil {
		return m.updateStoreVisitFn(name, cfg)
	}
	return cfg, nil
}

func (m *fakeConfigManager) DeleteStoreVisitor(name string) error {
	if m.deleteStoreVisitFn != nil {
		return m.deleteStoreVisitFn(name)
	}
	return nil
}

func (m *fakeConfigManager) GracefulClose(d time.Duration) {
	if m.gracefulCloseFn != nil {
		m.gracefulCloseFn(d)
	}
}

func newRawTCPProxyConfig(name string) *v1.TCPProxyConfig {
	return &v1.TCPProxyConfig{
		ProxyBaseConfig: v1.ProxyBaseConfig{
			Name: name,
			Type: "tcp",
			ProxyBackend: v1.ProxyBackend{
				LocalPort: 10080,
			},
		},
	}
}

func TestBuildProxyStatusRespStoreSourceEnabled(t *testing.T) {
	status := &proxy.WorkingStatus{
		Name:       "shared-proxy",
		Type:       "tcp",
		Phase:      proxy.ProxyPhaseRunning,
		RemoteAddr: ":8080",
		Cfg:        newRawTCPProxyConfig("shared-proxy"),
	}

	controller := &Controller{
		serverAddr: "127.0.0.1",
		manager: &fakeConfigManager{
			isStoreProxyEnabledFn: func(name string) bool {
				return name == "shared-proxy"
			},
		},
	}

	resp := controller.buildProxyStatusResp(status)
	if resp.Source != "store" {
		t.Fatalf("unexpected source: %q", resp.Source)
	}
	if resp.RemoteAddr != "127.0.0.1:8080" {
		t.Fatalf("unexpected remote addr: %q", resp.RemoteAddr)
	}
}

func TestReloadErrorMapping(t *testing.T) {
	tests := []struct {
		name         string
		err          error
		expectedCode int
	}{
		{name: "invalid arg", err: fmtError(configmgmt.ErrInvalidArgument, "bad cfg"), expectedCode: http.StatusBadRequest},
		{name: "apply fail", err: fmtError(configmgmt.ErrApplyConfig, "reload failed"), expectedCode: http.StatusInternalServerError},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			controller := &Controller{
				manager: &fakeConfigManager{reloadFromFileFn: func(bool) error { return tc.err }},
			}
			ctx := httppkg.NewContext(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/api/reload", nil))
			_, err := controller.Reload(ctx)
			if err == nil {
				t.Fatal("expected error")
			}
			assertHTTPCode(t, err, tc.expectedCode)
		})
	}
}

func TestStoreProxyErrorMapping(t *testing.T) {
	tests := []struct {
		name         string
		err          error
		expectedCode int
	}{
		{name: "not found", err: fmtError(configmgmt.ErrNotFound, "not found"), expectedCode: http.StatusNotFound},
		{name: "conflict", err: fmtError(configmgmt.ErrConflict, "exists"), expectedCode: http.StatusConflict},
		{name: "internal", err: errors.New("persist failed"), expectedCode: http.StatusInternalServerError},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			body := []byte(`{"name":"shared-proxy","type":"tcp","tcp":{"localPort":10080}}`)
			req := httptest.NewRequest(http.MethodPut, "/api/store/proxies/shared-proxy", bytes.NewReader(body))
			req = mux.SetURLVars(req, map[string]string{"name": "shared-proxy"})
			ctx := httppkg.NewContext(httptest.NewRecorder(), req)

			controller := &Controller{
				manager: &fakeConfigManager{
					updateStoreProxyFn: func(_ string, _ v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
						return nil, tc.err
					},
				},
			}

			_, err := controller.UpdateStoreProxy(ctx)
			if err == nil {
				t.Fatal("expected error")
			}
			assertHTTPCode(t, err, tc.expectedCode)
		})
	}
}

func TestStoreVisitorErrorMapping(t *testing.T) {
	body := []byte(`{"name":"shared-visitor","type":"xtcp","xtcp":{"serverName":"server","bindPort":10081,"secretKey":"secret"}}`)
	req := httptest.NewRequest(http.MethodDelete, "/api/store/visitors/shared-visitor", bytes.NewReader(body))
	req = mux.SetURLVars(req, map[string]string{"name": "shared-visitor"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	controller := &Controller{
		manager: &fakeConfigManager{
			deleteStoreVisitFn: func(string) error {
				return fmtError(configmgmt.ErrStoreDisabled, "disabled")
			},
		},
	}

	_, err := controller.DeleteStoreVisitor(ctx)
	if err == nil {
		t.Fatal("expected error")
	}
	assertHTTPCode(t, err, http.StatusNotFound)
}

func TestCreateStoreProxyIgnoresUnknownFields(t *testing.T) {
	var gotName string
	controller := &Controller{
		manager: &fakeConfigManager{
			createStoreProxyFn: func(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
				gotName = cfg.GetBaseConfig().Name
				return cfg, nil
			},
		},
	}

	body := []byte(`{"name":"raw-proxy","type":"tcp","unexpected":"value","tcp":{"localPort":10080,"unknownInBlock":"value"}}`)
	req := httptest.NewRequest(http.MethodPost, "/api/store/proxies", bytes.NewReader(body))
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.CreateStoreProxy(ctx)
	if err != nil {
		t.Fatalf("create store proxy: %v", err)
	}
	if gotName != "raw-proxy" {
		t.Fatalf("unexpected proxy name: %q", gotName)
	}

	payload, ok := resp.(model.ProxyDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.Type != "tcp" || payload.TCP == nil {
		t.Fatalf("unexpected payload: %#v", payload)
	}
}

func TestCreateStoreVisitorIgnoresUnknownFields(t *testing.T) {
	var gotName string
	controller := &Controller{
		manager: &fakeConfigManager{
			createStoreVisitFn: func(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
				gotName = cfg.GetBaseConfig().Name
				return cfg, nil
			},
		},
	}

	body := []byte(`{
			"name":"raw-visitor","type":"xtcp","unexpected":"value",
			"xtcp":{"serverName":"server","bindPort":10081,"secretKey":"secret","unknownInBlock":"value"}
		}`)
	req := httptest.NewRequest(http.MethodPost, "/api/store/visitors", bytes.NewReader(body))
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.CreateStoreVisitor(ctx)
	if err != nil {
		t.Fatalf("create store visitor: %v", err)
	}
	if gotName != "raw-visitor" {
		t.Fatalf("unexpected visitor name: %q", gotName)
	}

	payload, ok := resp.(model.VisitorDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.Type != "xtcp" || payload.XTCP == nil {
		t.Fatalf("unexpected payload: %#v", payload)
	}
}

func TestCreateStoreProxyPluginUnknownFieldsAreIgnored(t *testing.T) {
	var gotPluginType string
	controller := &Controller{
		manager: &fakeConfigManager{
			createStoreProxyFn: func(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
				gotPluginType = cfg.GetBaseConfig().Plugin.Type
				return cfg, nil
			},
		},
	}

	body := []byte(`{"name":"plugin-proxy","type":"tcp","tcp":{"plugin":{"type":"http2https","localAddr":"127.0.0.1:8080","unknownInPlugin":"value"}}}`)
	req := httptest.NewRequest(http.MethodPost, "/api/store/proxies", bytes.NewReader(body))
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.CreateStoreProxy(ctx)
	if err != nil {
		t.Fatalf("create store proxy: %v", err)
	}
	if gotPluginType != "http2https" {
		t.Fatalf("unexpected plugin type: %q", gotPluginType)
	}
	payload, ok := resp.(model.ProxyDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.TCP == nil {
		t.Fatalf("unexpected response payload: %#v", payload)
	}
	pluginType := payload.TCP.Plugin.Type

	if pluginType != "http2https" {
		t.Fatalf("unexpected plugin type in response payload: %q", pluginType)
	}
}

func TestCreateStoreVisitorPluginUnknownFieldsAreIgnored(t *testing.T) {
	var gotPluginType string
	controller := &Controller{
		manager: &fakeConfigManager{
			createStoreVisitFn: func(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
				gotPluginType = cfg.GetBaseConfig().Plugin.Type
				return cfg, nil
			},
		},
	}

	body := []byte(`{
			"name":"plugin-visitor","type":"stcp",
			"stcp":{"serverName":"server","bindPort":10081,"plugin":{"type":"virtual_net","destinationIP":"10.0.0.1","unknownInPlugin":"value"}}
		}`)
	req := httptest.NewRequest(http.MethodPost, "/api/store/visitors", bytes.NewReader(body))
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.CreateStoreVisitor(ctx)
	if err != nil {
		t.Fatalf("create store visitor: %v", err)
	}
	if gotPluginType != "virtual_net" {
		t.Fatalf("unexpected plugin type: %q", gotPluginType)
	}
	payload, ok := resp.(model.VisitorDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.STCP == nil {
		t.Fatalf("unexpected response payload: %#v", payload)
	}
	pluginType := payload.STCP.Plugin.Type

	if pluginType != "virtual_net" {
		t.Fatalf("unexpected plugin type in response payload: %q", pluginType)
	}
}

func TestUpdateStoreProxyRejectsMismatchedTypeBlock(t *testing.T) {
	controller := &Controller{manager: &fakeConfigManager{}}
	body := []byte(`{"name":"p1","type":"tcp","udp":{"localPort":10080}}`)
	req := httptest.NewRequest(http.MethodPut, "/api/store/proxies/p1", bytes.NewReader(body))
	req = mux.SetURLVars(req, map[string]string{"name": "p1"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	_, err := controller.UpdateStoreProxy(ctx)
	if err == nil {
		t.Fatal("expected error")
	}
	assertHTTPCode(t, err, http.StatusBadRequest)
}

func TestUpdateStoreProxyRejectsNameMismatch(t *testing.T) {
	controller := &Controller{manager: &fakeConfigManager{}}
	body := []byte(`{"name":"p2","type":"tcp","tcp":{"localPort":10080}}`)
	req := httptest.NewRequest(http.MethodPut, "/api/store/proxies/p1", bytes.NewReader(body))
	req = mux.SetURLVars(req, map[string]string{"name": "p1"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	_, err := controller.UpdateStoreProxy(ctx)
	if err == nil {
		t.Fatal("expected error")
	}
	assertHTTPCode(t, err, http.StatusBadRequest)
}

func TestListStoreProxiesReturnsSortedPayload(t *testing.T) {
	controller := &Controller{
		manager: &fakeConfigManager{
			listStoreProxiesFn: func() ([]v1.ProxyConfigurer, error) {
				b := newRawTCPProxyConfig("b")
				a := newRawTCPProxyConfig("a")
				return []v1.ProxyConfigurer{b, a}, nil
			},
		},
	}
	ctx := httppkg.NewContext(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/api/store/proxies", nil))

	resp, err := controller.ListStoreProxies(ctx)
	if err != nil {
		t.Fatalf("list store proxies: %v", err)
	}
	out, ok := resp.(model.ProxyListResp)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if len(out.Proxies) != 2 {
		t.Fatalf("unexpected proxy count: %d", len(out.Proxies))
	}
	if out.Proxies[0].Name != "a" || out.Proxies[1].Name != "b" {
		t.Fatalf("proxies are not sorted by name: %#v", out.Proxies)
	}
}

func fmtError(sentinel error, msg string) error {
	return fmt.Errorf("%w: %s", sentinel, msg)
}

func assertHTTPCode(t *testing.T, err error, expected int) {
	t.Helper()
	var httpErr *httppkg.Error
	if !errors.As(err, &httpErr) {
		t.Fatalf("unexpected error type: %T", err)
	}
	if httpErr.Code != expected {
		t.Fatalf("unexpected status code: got %d, want %d", httpErr.Code, expected)
	}
}

func TestUpdateStoreProxyReturnsTypedPayload(t *testing.T) {
	controller := &Controller{
		manager: &fakeConfigManager{
			updateStoreProxyFn: func(_ string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
				return cfg, nil
			},
		},
	}

	body := map[string]any{
		"name": "shared-proxy",
		"type": "tcp",
		"tcp": map[string]any{
			"localPort":  10080,
			"remotePort": 7000,
		},
	}
	data, err := json.Marshal(body)
	if err != nil {
		t.Fatalf("marshal request: %v", err)
	}

	req := httptest.NewRequest(http.MethodPut, "/api/store/proxies/shared-proxy", bytes.NewReader(data))
	req = mux.SetURLVars(req, map[string]string{"name": "shared-proxy"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.UpdateStoreProxy(ctx)
	if err != nil {
		t.Fatalf("update store proxy: %v", err)
	}
	payload, ok := resp.(model.ProxyDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.TCP == nil || payload.TCP.RemotePort != 7000 {
		t.Fatalf("unexpected response payload: %#v", payload)
	}
}

func TestGetProxyConfigFromManager(t *testing.T) {
	controller := &Controller{
		manager: &fakeConfigManager{
			getProxyConfigFn: func(name string) (v1.ProxyConfigurer, bool) {
				if name == "ssh" {
					cfg := &v1.TCPProxyConfig{
						ProxyBaseConfig: v1.ProxyBaseConfig{
							Name: "ssh",
							Type: "tcp",
							ProxyBackend: v1.ProxyBackend{
								LocalPort: 22,
							},
						},
					}
					return cfg, true
				}
				return nil, false
			},
		},
	}

	req := httptest.NewRequest(http.MethodGet, "/api/proxy/ssh/config", nil)
	req = mux.SetURLVars(req, map[string]string{"name": "ssh"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.GetProxyConfig(ctx)
	if err != nil {
		t.Fatalf("get proxy config: %v", err)
	}
	payload, ok := resp.(model.ProxyDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.Name != "ssh" || payload.Type != "tcp" || payload.TCP == nil {
		t.Fatalf("unexpected payload: %#v", payload)
	}
}

func TestGetProxyConfigNotFound(t *testing.T) {
	controller := &Controller{
		manager: &fakeConfigManager{
			getProxyConfigFn: func(name string) (v1.ProxyConfigurer, bool) {
				return nil, false
			},
		},
	}

	req := httptest.NewRequest(http.MethodGet, "/api/proxy/missing/config", nil)
	req = mux.SetURLVars(req, map[string]string{"name": "missing"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	_, err := controller.GetProxyConfig(ctx)
	if err == nil {
		t.Fatal("expected error")
	}
	assertHTTPCode(t, err, http.StatusNotFound)
}

func TestGetVisitorConfigFromManager(t *testing.T) {
	controller := &Controller{
		manager: &fakeConfigManager{
			getVisitorConfigFn: func(name string) (v1.VisitorConfigurer, bool) {
				if name == "my-stcp" {
					cfg := &v1.STCPVisitorConfig{
						VisitorBaseConfig: v1.VisitorBaseConfig{
							Name:       "my-stcp",
							Type:       "stcp",
							ServerName: "server1",
							BindPort:   9000,
						},
					}
					return cfg, true
				}
				return nil, false
			},
		},
	}

	req := httptest.NewRequest(http.MethodGet, "/api/visitor/my-stcp/config", nil)
	req = mux.SetURLVars(req, map[string]string{"name": "my-stcp"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	resp, err := controller.GetVisitorConfig(ctx)
	if err != nil {
		t.Fatalf("get visitor config: %v", err)
	}
	payload, ok := resp.(model.VisitorDefinition)
	if !ok {
		t.Fatalf("unexpected response type: %T", resp)
	}
	if payload.Name != "my-stcp" || payload.Type != "stcp" || payload.STCP == nil {
		t.Fatalf("unexpected payload: %#v", payload)
	}
}

func TestGetVisitorConfigNotFound(t *testing.T) {
	controller := &Controller{
		manager: &fakeConfigManager{
			getVisitorConfigFn: func(name string) (v1.VisitorConfigurer, bool) {
				return nil, false
			},
		},
	}

	req := httptest.NewRequest(http.MethodGet, "/api/visitor/missing/config", nil)
	req = mux.SetURLVars(req, map[string]string{"name": "missing"})
	ctx := httppkg.NewContext(httptest.NewRecorder(), req)

	_, err := controller.GetVisitorConfig(ctx)
	if err == nil {
		t.Fatal("expected error")
	}
	assertHTTPCode(t, err, http.StatusNotFound)
}


================================================
FILE: client/http/model/proxy_definition.go
================================================
package model

import (
	"fmt"
	"strings"

	v1 "github.com/fatedier/frp/pkg/config/v1"
)

type ProxyDefinition struct {
	Name string `json:"name"`
	Type string `json:"type"`

	TCP    *v1.TCPProxyConfig    `json:"tcp,omitempty"`
	UDP    *v1.UDPProxyConfig    `json:"udp,omitempty"`
	HTTP   *v1.HTTPProxyConfig   `json:"http,omitempty"`
	HTTPS  *v1.HTTPSProxyConfig  `json:"https,omitempty"`
	TCPMux *v1.TCPMuxProxyConfig `json:"tcpmux,omitempty"`
	STCP   *v1.STCPProxyConfig   `json:"stcp,omitempty"`
	SUDP   *v1.SUDPProxyConfig   `json:"sudp,omitempty"`
	XTCP   *v1.XTCPProxyConfig   `json:"xtcp,omitempty"`
}

func (p *ProxyDefinition) Validate(pathName string, isUpdate bool) error {
	if strings.TrimSpace(p.Name) == "" {
		return fmt.Errorf("proxy name is required")
	}
	if !IsProxyType(p.Type) {
		return fmt.Errorf("invalid proxy type: %s", p.Type)
	}
	if isUpdate && pathName != "" && pathName != p.Name {
		return fmt.Errorf("proxy name in URL must match name in body")
	}

	_, blockType, blockCount := p.activeBlock()
	if blockCount != 1 {
		return fmt.Errorf("exactly one proxy type block is required")
	}
	if blockType != p.Type {
		return fmt.Errorf("proxy type block %q does not match type %q", blockType, p.Type)
	}
	return nil
}

func (p *ProxyDefinition) ToConfigurer() (v1.ProxyConfigurer, error) {
	block, _, _ := p.activeBlock()
	if block == nil {
		return nil, fmt.Errorf("exactly one proxy type block is required")
	}

	cfg := block
	cfg.GetBaseConfig().Name = p.Name
	cfg.GetBaseConfig().Type = p.Type
	return cfg, nil
}

func ProxyDefinitionFromConfigurer(cfg v1.ProxyConfigurer) (ProxyDefinition, error) {
	if cfg == nil {
		return ProxyDefinition{}, fmt.Errorf("proxy config is nil")
	}

	base := cfg.GetBaseConfig()
	payload := ProxyDefinition{
		Name: base.Name,
		Type: base.Type,
	}

	switch c := cfg.(type) {
	case *v1.TCPProxyConfig:
		payload.TCP = c
	case *v1.UDPProxyConfig:
		payload.UDP = c
	case *v1.HTTPProxyConfig:
		payload.HTTP = c
	case *v1.HTTPSProxyConfig:
		payload.HTTPS = c
	case *v1.TCPMuxProxyConfig:
		payload.TCPMux = c
	case *v1.STCPProxyConfig:
		payload.STCP = c
	case *v1.SUDPProxyConfig:
		payload.SUDP = c
	case *v1.XTCPProxyConfig:
		payload.XTCP = c
	default:
		return ProxyDefinition{}, fmt.Errorf("unsupported proxy configurer type %T", cfg)
	}

	return payload, nil
}

func (p *ProxyDefinition) activeBlock() (v1.ProxyConfigurer, string, int) {
	count := 0
	var block v1.ProxyConfigurer
	var blockType string

	if p.TCP != nil {
		count++
		block = p.TCP
		blockType = "tcp"
	}
	if p.UDP != nil {
		count++
		block = p.UDP
		blockType = "udp"
	}
	if p.HTTP != nil {
		count++
		block = p.HTTP
		blockType = "http"
	}
	if p.HTTPS != nil {
		count++
		block = p.HTTPS
		blockType = "https"
	}
	if p.TCPMux != nil {
		count++
		block = p.TCPMux
		blockType = "tcpmux"
	}
	if p.STCP != nil {
		count++
		block = p.STCP
		blockType = "stcp"
	}
	if p.SUDP != nil {
		count++
		block = p.SUDP
		blockType = "sudp"
	}
	if p.XTCP != nil {
		count++
		block = p.XTCP
		blockType = "xtcp"
	}

	return block, blockType, count
}

func IsProxyType(typ string) bool {
	switch typ {
	case "tcp", "udp", "http", "https", "tcpmux", "stcp", "sudp", "xtcp":
		return true
	default:
		return false
	}
}


================================================
FILE: client/http/model/types.go
================================================
// Copyright 2025 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package model

const SourceStore = "store"

// StatusResp is the response for GET /api/status
type StatusResp map[string][]ProxyStatusResp

// ProxyStatusResp contains proxy status information
type ProxyStatusResp struct {
	Name       string `json:"name"`
	Type       string `json:"type"`
	Status     string `json:"status"`
	Err        string `json:"err"`
	LocalAddr  string `json:"local_addr"`
	Plugin     string `json:"plugin"`
	RemoteAddr string `json:"remote_addr"`
	Source     string `json:"source,omitempty"` // "store" or "config"
}

// ProxyListResp is the response for GET /api/store/proxies
type ProxyListResp struct {
	Proxies []ProxyDefinition `json:"proxies"`
}

// VisitorListResp is the response for GET /api/store/visitors
type VisitorListResp struct {
	Visitors []VisitorDefinition `json:"visitors"`
}


================================================
FILE: client/http/model/visitor_definition.go
================================================
package model

import (
	"fmt"
	"strings"

	v1 "github.com/fatedier/frp/pkg/config/v1"
)

type VisitorDefinition struct {
	Name string `json:"name"`
	Type string `json:"type"`

	STCP *v1.STCPVisitorConfig `json:"stcp,omitempty"`
	SUDP *v1.SUDPVisitorConfig `json:"sudp,omitempty"`
	XTCP *v1.XTCPVisitorConfig `json:"xtcp,omitempty"`
}

func (p *VisitorDefinition) Validate(pathName string, isUpdate bool) error {
	if strings.TrimSpace(p.Name) == "" {
		return fmt.Errorf("visitor name is required")
	}
	if !IsVisitorType(p.Type) {
		return fmt.Errorf("invalid visitor type: %s", p.Type)
	}
	if isUpdate && pathName != "" && pathName != p.Name {
		return fmt.Errorf("visitor name in URL must match name in body")
	}

	_, blockType, blockCount := p.activeBlock()
	if blockCount != 1 {
		return fmt.Errorf("exactly one visitor type block is required")
	}
	if blockType != p.Type {
		return fmt.Errorf("visitor type block %q does not match type %q", blockType, p.Type)
	}
	return nil
}

func (p *VisitorDefinition) ToConfigurer() (v1.VisitorConfigurer, error) {
	block, _, _ := p.activeBlock()
	if block == nil {
		return nil, fmt.Errorf("exactly one visitor type block is required")
	}

	cfg := block
	cfg.GetBaseConfig().Name = p.Name
	cfg.GetBaseConfig().Type = p.Type
	return cfg, nil
}

func VisitorDefinitionFromConfigurer(cfg v1.VisitorConfigurer) (VisitorDefinition, error) {
	if cfg == nil {
		return VisitorDefinition{}, fmt.Errorf("visitor config is nil")
	}

	base := cfg.GetBaseConfig()
	payload := VisitorDefinition{
		Name: base.Name,
		Type: base.Type,
	}

	switch c := cfg.(type) {
	case *v1.STCPVisitorConfig:
		payload.STCP = c
	case *v1.SUDPVisitorConfig:
		payload.SUDP = c
	case *v1.XTCPVisitorConfig:
		payload.XTCP = c
	default:
		return VisitorDefinition{}, fmt.Errorf("unsupported visitor configurer type %T", cfg)
	}

	return payload, nil
}

func (p *VisitorDefinition) activeBlock() (v1.VisitorConfigurer, string, int) {
	count := 0
	var block v1.VisitorConfigurer
	var blockType string

	if p.STCP != nil {
		count++
		block = p.STCP
		blockType = "stcp"
	}
	if p.SUDP != nil {
		count++
		block = p.SUDP
		blockType = "sudp"
	}
	if p.XTCP != nil {
		count++
		block = p.XTCP
		blockType = "xtcp"
	}
	return block, blockType, count
}

func IsVisitorType(typ string) bool {
	switch typ {
	case "stcp", "sudp", "xtcp":
		return true
	default:
		return false
	}
}


================================================
FILE: client/proxy/general_tcp.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package proxy

import (
	"reflect"

	v1 "github.com/fatedier/frp/pkg/config/v1"
)

func init() {
	pxyConfs := []v1.ProxyConfigurer{
		&v1.TCPProxyConfig{},
		&v1.HTTPProxyConfig{},
		&v1.HTTPSProxyConfig{},
		&v1.STCPProxyConfig{},
		&v1.TCPMuxProxyConfig{},
	}
	for _, cfg := range pxyConfs {
		RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
	}
}

// GeneralTCPProxy is a general implementation of Proxy interface for TCP protocol.
// If the default GeneralTCPProxy cannot meet the requirements, you can customize
// the implementation of the Proxy interface.
type GeneralTCPProxy struct {
	*BaseProxy
}

func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy {
	return &GeneralTCPProxy{
		BaseProxy: baseProxy,
	}
}


================================================
FILE: client/proxy/proxy.go
================================================
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package proxy

import (
	"context"
	"fmt"
	"io"
	"net"
	"reflect"
	"strconv"
	"sync"
	"time"

	libio "github.com/fatedier/golib/io"
	libnet "github.com/fatedier/golib/net"
	"golang.org/x/time/rate"

	"github.com/fatedier/frp/pkg/config/types"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	plugin "github.com/fatedier/frp/pkg/plugin/client"
	"github.com/fatedier/frp/pkg/transport"
	"github.com/fatedier/frp/pkg/util/limit"
	netpkg "github.com/fatedier/frp/pkg/util/net"
	"github.com/fatedier/frp/pkg/util/xlog"
	"github.com/fatedier/frp/pkg/vnet"
)

var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}

func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, v1.ProxyConfigurer) Proxy) {
	proxyFactoryRegistry[proxyConfType] = factory
}

// Proxy defines how to handle work connections for different proxy type.
type Proxy interface {
	Run() error
	// InWorkConn accept work connections registered to server.
	InWorkConn(net.Conn, *msg.StartWorkConn)
	SetInWorkConnCallback(func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) /* continue */ bool)
	Close()
}

func NewProxy(
	ctx context.Context,
	pxyConf v1.ProxyConfigurer,
	clientCfg *v1.ClientCommonConfig,
	encryptionKey []byte,
	msgTransporter transport.MessageTransporter,
	vnetController *vnet.Controller,
) (pxy Proxy) {
	var limiter *rate.Limiter
	limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
	if limitBytes > 0 && pxyConf.GetBaseConfig().Transport.BandwidthLimitMode == types.BandwidthLimitModeClient {
		limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
	}

	baseProxy := BaseProxy{
		baseCfg:        pxyConf.GetBaseConfig(),
		clientCfg:      clientCfg,
		encryptionKey:  encryptionKey,
		limiter:        limiter,
		msgTransporter: msgTransporter,
		vnetController: vnetController,
		xl:             xlog.FromContextSafe(ctx),
		ctx:            ctx,
	}

	factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
	if factory == nil {
		return nil
	}
	return factory(&baseProxy, pxyConf)
}

type BaseProxy struct {
	baseCfg        *v1.ProxyBaseConfig
	clientCfg      *v1.ClientCommonConfig
	encryptionKey  []byte
	msgTransporter transport.MessageTransporter
	vnetController *vnet.Controller
	limiter        *rate.Limiter
	// proxyPlugin is used to handle connections instead of dialing to local service.
	// It's only validate for TCP protocol now.
	proxyPlugin        plugin.Plugin
	inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) /* continue */ bool

	mu  sync.RWMutex
	xl  *xlog.Logger
	ctx context.Context
}

func (pxy *BaseProxy) Run() error {
	if pxy.baseCfg.Plugin.Type != "" {
		p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
			Name:           pxy.baseCfg.Name,
			VnetController: pxy.vnetController,
		}, pxy.baseCfg.Plugin.ClientPluginOptions)
		if err != nil {
			return err
		}
		pxy.proxyPlugin = p
	}
	return nil
}

func (pxy *BaseProxy) Close() {
	if pxy.proxyPlugin != nil {
		pxy.proxyPlugin.Close()
	}
}

// wrapWorkConn applies rate limiting, encryption, and compression
// to a work connection based on the proxy's transport configuration.
// The returned recycle function should be called when the stream is no longer in use
// to return compression resources to the pool. It is safe to not call recycle,
// in which case resources will be garbage collected normally.
func (pxy *BaseProxy) wrapWorkConn(conn net.Conn, encKey []byte) (io.ReadWriteCloser, func(), error) {
	var rwc io.ReadWriteCloser = conn
	if pxy.limiter != nil {
		rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
			return conn.Close()
		})
	}
	if pxy.baseCfg.Transport.UseEncryption {
		var err error
		rwc, err = libio.WithEncryption(rwc, encKey)
		if err != nil {
			conn.Close()
			return nil, nil, fmt.Errorf("create encryption stream error: %w", err)
		}
	}
	var recycleFn func()
	if pxy.baseCfg.Transport.UseCompression {
		rwc, recycleFn = libio.WithCompressionFromPool(rwc)
	}
	return rwc, recycleFn, nil
}

func (pxy *BaseProxy) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
	pxy.inWorkConnCallback = cb
}

func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
	if pxy.inWorkConnCallback != nil {
		if !pxy.inWorkConnCallback(pxy.baseCfg, conn, m) {
			return
		}
	}
	pxy.HandleTCPWorkConnection(conn, m, pxy.encryptionKey)
}

// Common handler for tcp work connections.
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
	xl := pxy.xl
	baseCfg := pxy.baseCfg

	xl.Tracef("handle tcp work connection, useEncryption: %t, useCompression: %t",
		baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)

	remote, recycleFn, err := pxy.wrapWorkConn(workConn, encKey)
	if err != nil {
		xl.Errorf("wrap work connection: %v", err)
		return
	}

	// check if we need to send proxy protocol info
	var connInfo plugin.ConnectionInfo
	if m.SrcAddr != "" && m.SrcPort != 0 {
		if m.DstAddr == "" {
			m.DstAddr = "127.0.0.1"
		}
		srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
		dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
		connInfo.SrcAddr = srcAddr
		connInfo.DstAddr = dstAddr
	}

	if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
		header := netpkg.BuildProxyProtocolHeaderStruct(connInfo.SrcAddr, connInfo.DstAddr, baseCfg.Transport.ProxyProtocolVersion)
		connInfo.ProxyProtocolHeader = header
	}
	connInfo.Conn = remote
	connInfo.UnderlyingConn = workConn

	if pxy.proxyPlugin != nil {
		// if plugin is set, let plugin handle connection first
		// Don't recycle compression resources here because plugins may
		// retain the connection after Handle returns.
		xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
		pxy.proxyPlugin.Handle(pxy.ctx, &connInfo)
		xl.Debugf("handle by plugin finished")
		return
	}

	if recycleFn != nil {
		defer recycleFn()
	}

	localConn, err := libnet.Dial(
		net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort)),
		libnet.WithTimeout(10*time.Second),
	)
	if err != nil {
		workConn.Close()
		xl.Errorf("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err)
		return
	}

	xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
		localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())

	if connInfo.ProxyProtocolHeader != nil {
		if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
			workConn.Close()
			localConn.Close()
			xl.Errorf("write proxy protocol header to local conn error: %v", err)
			return
		}
	}

	_, _, errs := libio.Join(localConn, remote)
	xl.Debugf("join connections closed")
	if len(errs) > 0 {
		xl.Tracef("join connections errors: %v", errs)
	}
}


================================================
FILE: client/proxy/proxy_manager.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package proxy

import (
	"context"
	"fmt"
	"net"
	"reflect"
	"sync"

	"github.com/samber/lo"

	"github.com/fatedier/frp/client/event"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/transport"
	"github.com/fatedier/frp/pkg/util/xlog"
	"github.com/fatedier/frp/pkg/vnet"
)

type Manager struct {
	proxies            map[string]*Wrapper
	msgTransporter     transport.MessageTransporter
	inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
	vnetController     *vnet.Controller

	closed bool
	mu     sync.RWMutex

	encryptionKey []byte
	clientCfg     *v1.ClientCommonConfig

	ctx context.Context
}

func NewManager(
	ctx context.Context,
	clientCfg *v1.ClientCommonConfig,
	encryptionKey []byte,
	msgTransporter transport.MessageTransporter,
	vnetController *vnet.Controller,
) *Manager {
	return &Manager{
		proxies:        make(map[string]*Wrapper),
		msgTransporter: msgTransporter,
		vnetController: vnetController,
		closed:         false,
		encryptionKey:  encryptionKey,
		clientCfg:      clientCfg,
		ctx:            ctx,
	}
}

func (pm *Manager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
	pm.mu.RLock()
	pxy, ok := pm.proxies[name]
	pm.mu.RUnlock()
	if !ok {
		return fmt.Errorf("proxy [%s] not found", name)
	}

	err := pxy.SetRunningStatus(remoteAddr, serverRespErr)
	if err != nil {
		return err
	}
	return nil
}

func (pm *Manager) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
	pm.inWorkConnCallback = cb
}

func (pm *Manager) Close() {
	pm.mu.Lock()
	defer pm.mu.Unlock()
	for _, pxy := range pm.proxies {
		pxy.Stop()
	}
	pm.proxies = make(map[string]*Wrapper)
}

func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
	pm.mu.RLock()
	pw, ok := pm.proxies[name]
	pm.mu.RUnlock()
	if ok {
		pw.InWorkConn(workConn, m)
	} else {
		workConn.Close()
	}
}

func (pm *Manager) HandleEvent(payload any) error {
	var m msg.Message
	switch e := payload.(type) {
	case *event.StartProxyPayload:
		m = e.NewProxyMsg
	case *event.CloseProxyPayload:
		m = e.CloseProxyMsg
	default:
		return event.ErrPayloadType
	}

	return pm.msgTransporter.Send(m)
}

func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
	pm.mu.RLock()
	defer pm.mu.RUnlock()
	ps := make([]*WorkingStatus, 0, len(pm.proxies))
	for _, pxy := range pm.proxies {
		ps = append(ps, pxy.GetStatus())
	}
	return ps
}

func (pm *Manager) GetProxyStatus(name string) (*WorkingStatus, bool) {
	pm.mu.RLock()
	defer pm.mu.RUnlock()
	if pxy, ok := pm.proxies[name]; ok {
		return pxy.GetStatus(), true
	}
	return nil, false
}

func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
	xl := xlog.FromContextSafe(pm.ctx)
	proxyCfgsMap := lo.KeyBy(proxyCfgs, func(c v1.ProxyConfigurer) string {
		return c.GetBaseConfig().Name
	})
	pm.mu.Lock()
	defer pm.mu.Unlock()

	delPxyNames := make([]string, 0)
	for name, pxy := range pm.proxies {
		del := false
		cfg, ok := proxyCfgsMap[name]
		if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
			del = true
		}

		if del {
			delPxyNames = append(delPxyNames, name)
			delete(pm.proxies, name)
			pxy.Stop()
		}
	}
	if len(delPxyNames) > 0 {
		xl.Infof("proxy removed: %s", delPxyNames)
	}

	addPxyNames := make([]string, 0)
	for _, cfg := range proxyCfgs {
		name := cfg.GetBaseConfig().Name
		if _, ok := pm.proxies[name]; !ok {
			pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.encryptionKey, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
			if pm.inWorkConnCallback != nil {
				pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
			}
			pm.proxies[name] = pxy
			addPxyNames = append(addPxyNames, name)

			pxy.Start()
		}
	}
	if len(addPxyNames) > 0 {
		xl.Infof("proxy added: %s", addPxyNames)
	}
}


================================================
FILE: client/proxy/proxy_wrapper.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package proxy

import (
	"context"
	"fmt"
	"net"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fatedier/golib/errors"

	"github.com/fatedier/frp/client/event"
	"github.com/fatedier/frp/client/health"
	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/naming"
	"github.com/fatedier/frp/pkg/transport"
	"github.com/fatedier/frp/pkg/util/xlog"
	"github.com/fatedier/frp/pkg/vnet"
)

const (
	ProxyPhaseNew         = "new"
	ProxyPhaseWaitStart   = "wait start"
	ProxyPhaseStartErr    = "start error"
	ProxyPhaseRunning     = "running"
	ProxyPhaseCheckFailed = "check failed"
	ProxyPhaseClosed      = "closed"
)

var (
	statusCheckInterval = 3 * time.Second
	waitResponseTimeout = 20 * time.Second
	startErrTimeout     = 30 * time.Second
)

type WorkingStatus struct {
	Name  string             `json:"name"`
	Type  string             `json:"type"`
	Phase string             `json:"status"`
	Err   string             `json:"err"`
	Cfg   v1.ProxyConfigurer `json:"cfg"`

	// Got from server.
	RemoteAddr string `json:"remote_addr"`
}

type Wrapper struct {
	WorkingStatus

	// underlying proxy
	pxy Proxy

	// if ProxyConf has healcheck config
	// monitor will watch if it is alive
	monitor *health.Monitor

	// event handler
	handler event.Handler

	msgTransporter transport.MessageTransporter
	// vnet controller
	vnetController *vnet.Controller

	health           uint32
	lastSendStartMsg time.Time
	lastStartErr     time.Time
	closeCh          chan struct{}
	healthNotifyCh   chan struct{}
	mu               sync.RWMutex

	xl  *xlog.Logger
	ctx context.Context

	wireName string
}

func NewWrapper(
	ctx context.Context,
	cfg v1.ProxyConfigurer,
	clientCfg *v1.ClientCommonConfig,
	encryptionKey []byte,
	eventHandler event.Handler,
	msgTransporter transport.MessageTransporter,
	vnetController *vnet.Controller,
) *Wrapper {
	baseInfo := cfg.GetBaseConfig()
	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
	pw := &Wrapper{
		WorkingStatus: WorkingStatus{
			Name:  baseInfo.Name,
			Type:  baseInfo.Type,
			Phase: ProxyPhaseNew,
			Cfg:   cfg,
		},
		closeCh:        make(chan struct{}),
		healthNotifyCh: make(chan struct{}),
		handler:        eventHandler,
		msgTransporter: msgTransporter,
		vnetController: vnetController,
		xl:             xl,
		ctx:            xlog.NewContext(ctx, xl),
		wireName:       naming.AddUserPrefix(clientCfg.User, baseInfo.Name),
	}

	if baseInfo.HealthCheck.Type != "" && baseInfo.LocalPort > 0 {
		pw.health = 1 // means failed
		addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
		pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
			pw.statusNormalCallback, pw.statusFailedCallback)
		xl.Tracef("enable health check monitor")
	}

	pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, encryptionKey, pw.msgTransporter, pw.vnetController)
	return pw
}

func (pw *Wrapper) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
	pw.pxy.SetInWorkConnCallback(cb)
}

func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
	pw.mu.Lock()
	defer pw.mu.Unlock()
	if pw.Phase != ProxyPhaseWaitStart {
		return fmt.Errorf("status not wait start, ignore start message")
	}

	pw.RemoteAddr = remoteAddr
	if respErr != "" {
		pw.Phase = ProxyPhaseStartErr
		pw.Err = respErr
		pw.lastStartErr = time.Now()
		return fmt.Errorf("%s", pw.Err)
	}

	if err := pw.pxy.Run(); err != nil {
		pw.close()
		pw.Phase = ProxyPhaseStartErr
		pw.Err = err.Error()
		pw.lastStartErr = time.Now()
		return err
	}

	pw.Phase = ProxyPhaseRunning
	pw.Err = ""
	return nil
}

func (pw *Wrapper) Start() {
	go pw.checkWorker()
	if pw.monitor != nil {
		go pw.monitor.Start()
	}
}

func (pw *Wrapper) Stop() {
	pw.mu.Lock()
	defer pw.mu.Unlock()
	close(pw.closeCh)
	close(pw.healthNotifyCh)
	pw.pxy.Close()
	if pw.monitor != nil {
		pw.monitor.Stop()
	}
	pw.Phase = ProxyPhaseClosed
	pw.close()
}

func (pw *Wrapper) close() {
	_ = pw.handler(&event.CloseProxyPayload{
		CloseProxyMsg: &msg.CloseProxy{
			ProxyName: pw.wireName,
		},
	})
}

func (pw *Wrapper) checkWorker() {
	xl := pw.xl
	if pw.monitor != nil {
		// let monitor do check request first
		time.Sleep(500 * time.Millisecond)
	}
	for {
		// check proxy status
		now := time.Now()
		if atomic.LoadUint32(&pw.health) == 0 {
			pw.mu.Lock()
			if pw.Phase == ProxyPhaseNew ||
				pw.Phase == ProxyPhaseCheckFailed ||
				(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
				(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {

				xl.Tracef("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart)
				pw.Phase = ProxyPhaseWaitStart

				var newProxyMsg msg.NewProxy
				pw.Cfg.MarshalToMsg(&newProxyMsg)
				newProxyMsg.ProxyName = pw.wireName
				pw.lastSendStartMsg = now
				_ = pw.handler(&event.StartProxyPayload{
					NewProxyMsg: &newProxyMsg,
				})
			}
			pw.mu.Unlock()
		} else {
			pw.mu.Lock()
			if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart {
				pw.close()
				xl.Tracef("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed)
				pw.Phase = ProxyPhaseCheckFailed
			}
			pw.mu.Unlock()
		}

		select {
		case <-pw.closeCh:
			return
		case <-time.After(statusCheckInterval):
		case <-pw.healthNotifyCh:
		}
	}
}

func (pw *Wrapper) statusNormalCallback() {
	xl := pw.xl
	atomic.StoreUint32(&pw.health, 0)
	_ = errors.PanicToError(func() {
		select {
		case pw.healthNotifyCh <- struct{}{}:
		default:
		}
	})
	xl.Infof("health check success")
}

func (pw *Wrapper) statusFailedCallback() {
	xl := pw.xl
	atomic.StoreUint32(&pw.health, 1)
	_ = errors.PanicToError(func() {
		select {
		case pw.healthNotifyCh <- struct{}{}:
		default:
		}
	})
	xl.Infof("health check failed")
}

func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
	xl := pw.xl
	pw.mu.RLock()
	pxy := pw.pxy
	pw.mu.RUnlock()
	if pxy != nil && pw.Phase == ProxyPhaseRunning {
		xl.Debugf("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
		go pxy.InWorkConn(workConn, m)
	} else {
		workConn.Close()
	}
}

func (pw *Wrapper) GetStatus() *WorkingStatus {
	pw.mu.RLock()
	defer pw.mu.RUnlock()
	ps := &WorkingStatus{
		Name:       pw.Name,
		Type:       pw.Type,
		Phase:      pw.Phase,
		Err:        pw.Err,
		Cfg:        pw.Cfg,
		RemoteAddr: pw.RemoteAddr,
	}
	return ps
}


================================================
FILE: client/proxy/sudp.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !frps

package proxy

import (
	"net"
	"reflect"
	"strconv"
	"sync"
	"time"

	"github.com/fatedier/golib/errors"

	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/proto/udp"
	netpkg "github.com/fatedier/frp/pkg/util/net"
)

func init() {
	RegisterProxyFactory(reflect.TypeFor[*v1.SUDPProxyConfig](), NewSUDPProxy)
}

type SUDPProxy struct {
	*BaseProxy

	cfg *v1.SUDPProxyConfig

	localAddr *net.UDPAddr

	closeCh chan struct{}
}

func NewSUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
	unwrapped, ok := cfg.(*v1.SUDPProxyConfig)
	if !ok {
		return nil
	}
	return &SUDPProxy{
		BaseProxy: baseProxy,
		cfg:       unwrapped,
		closeCh:   make(chan struct{}),
	}
}

func (pxy *SUDPProxy) Run() (err error) {
	pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
	if err != nil {
		return
	}
	return
}

func (pxy *SUDPProxy) Close() {
	pxy.mu.Lock()
	defer pxy.mu.Unlock()
	select {
	case <-pxy.closeCh:
		return
	default:
		close(pxy.closeCh)
	}
}

func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
	xl := pxy.xl
	xl.Infof("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())

	remote, _, err := pxy.wrapWorkConn(conn, pxy.encryptionKey)
	if err != nil {
		xl.Errorf("wrap work connection: %v", err)
		return
	}

	workConn := netpkg.WrapReadWriteCloserToConn(remote, conn)
	readCh := make(chan *msg.UDPPacket, 1024)
	sendCh := make(chan msg.Message, 1024)
	isClose := false

	mu := &sync.Mutex{}

	closeFn := func() {
		mu.Lock()
		defer mu.Unlock()
		if isClose {
			return
		}

		isClose = true
		if workConn != nil {
			workConn.Close()
		}
		close(readCh)
		close(sendCh)
	}

	// udp service <- frpc <- frps <- frpc visitor <- user
	workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
		defer closeFn()

		for {
			// first to check sudp proxy is closed or not
			select {
			case <-pxy.closeCh:
				xl.Tracef("frpc sudp proxy is closed")
				return
			default:
			}

			var udpMsg msg.UDPPacket
			if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
				xl.Warnf("read from workConn for sudp error: %v", errRet)
				return
			}

			if errRet := errors.PanicToError(func() {
				readCh <- &udpMsg
			}); errRet != nil {
				xl.Warnf("reader goroutine for sudp work connection closed: %v", errRet)
				return
			}
		}
	}

	// udp service -> frpc -> frps -> frpc visitor -> user
	workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
		defer func() {
			closeFn()
			xl.Infof("writer goroutine for sudp work connection closed")
		}()

		var errRet error
		for rawMsg := range sendCh {
			switch m := rawMsg.(type) {
			case *msg.UDPPacket:
				xl.Tracef("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
					m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
			case *msg.Ping:
				xl.Tracef("frpc send ping message to frpc visitor")
			}

			if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
				xl.Errorf("sudp work write error: %v", errRet)
				return
			}
		}
	}

	heartbeatFn := func(sendCh chan msg.Message) {
		ticker := time.NewTicker(30 * time.Second)
		defer func() {
			ticker.Stop()
			closeFn()
		}()

		var errRet error
		for {
			select {
			case <-ticker.C:
				if errRet = errors.PanicToError(func() {
					sendCh <- &msg.Ping{}
				}); errRet != nil {
					xl.Warnf("heartbeat goroutine for sudp work connection closed")
					return
				}
			case <-pxy.closeCh:
				xl.Tracef("frpc sudp proxy is closed")
				return
			}
		}
	}

	go workConnSenderFn(workConn, sendCh)
	go workConnReaderFn(workConn, readCh)
	go heartbeatFn(sendCh)

	udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
}


================================================
FILE: client/proxy/udp.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !frps

package proxy

import (
	"net"
	"reflect"
	"strconv"
	"time"

	"github.com/fatedier/golib/errors"

	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/proto/udp"
	netpkg "github.com/fatedier/frp/pkg/util/net"
)

func init() {
	RegisterProxyFactory(reflect.TypeFor[*v1.UDPProxyConfig](), NewUDPProxy)
}

type UDPProxy struct {
	*BaseProxy

	cfg *v1.UDPProxyConfig

	localAddr *net.UDPAddr
	readCh    chan *msg.UDPPacket

	// include msg.UDPPacket and msg.Ping
	sendCh   chan msg.Message
	workConn net.Conn
	closed   bool
}

func NewUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
	unwrapped, ok := cfg.(*v1.UDPProxyConfig)
	if !ok {
		return nil
	}
	return &UDPProxy{
		BaseProxy: baseProxy,
		cfg:       unwrapped,
	}
}

func (pxy *UDPProxy) Run() (err error) {
	pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
	if err != nil {
		return
	}
	return
}

func (pxy *UDPProxy) Close() {
	pxy.mu.Lock()
	defer pxy.mu.Unlock()

	if !pxy.closed {
		pxy.closed = true
		if pxy.workConn != nil {
			pxy.workConn.Close()
		}
		if pxy.readCh != nil {
			close(pxy.readCh)
		}
		if pxy.sendCh != nil {
			close(pxy.sendCh)
		}
	}
}

func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
	xl := pxy.xl
	xl.Infof("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
	// close resources related with old workConn
	pxy.Close()

	remote, _, err := pxy.wrapWorkConn(conn, pxy.encryptionKey)
	if err != nil {
		xl.Errorf("wrap work connection: %v", err)
		return
	}

	pxy.mu.Lock()
	pxy.workConn = netpkg.WrapReadWriteCloserToConn(remote, conn)
	pxy.readCh = make(chan *msg.UDPPacket, 1024)
	pxy.sendCh = make(chan msg.Message, 1024)
	pxy.closed = false
	pxy.mu.Unlock()

	workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
		for {
			var udpMsg msg.UDPPacket
			if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
				xl.Warnf("read from workConn for udp error: %v", errRet)
				return
			}
			if errRet := errors.PanicToError(func() {
				xl.Tracef("get udp package from workConn, len: %d", len(udpMsg.Content))
				readCh <- &udpMsg
			}); errRet != nil {
				xl.Infof("reader goroutine for udp work connection closed: %v", errRet)
				return
			}
		}
	}
	workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
		defer func() {
			xl.Infof("writer goroutine for udp work connection closed")
		}()
		var errRet error
		for rawMsg := range sendCh {
			switch m := rawMsg.(type) {
			case *msg.UDPPacket:
				xl.Tracef("send udp package to workConn, len: %d", len(m.Content))
			case *msg.Ping:
				xl.Tracef("send ping message to udp workConn")
			}
			if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
				xl.Errorf("udp work write error: %v", errRet)
				return
			}
		}
	}
	heartbeatFn := func(sendCh chan msg.Message) {
		var errRet error
		for {
			time.Sleep(time.Duration(30) * time.Second)
			if errRet = errors.PanicToError(func() {
				sendCh <- &msg.Ping{}
			}); errRet != nil {
				xl.Tracef("heartbeat goroutine for udp work connection closed")
				break
			}
		}
	}

	go workConnSenderFn(pxy.workConn, pxy.sendCh)
	go workConnReaderFn(pxy.workConn, pxy.readCh)
	go heartbeatFn(pxy.sendCh)

	// Call Forwarder with proxy protocol version (empty string means no proxy protocol)
	udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
}


================================================
FILE: client/proxy/xtcp.go
================================================
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !frps

package proxy

import (
	"io"
	"net"
	"reflect"
	"time"

	fmux "github.com/hashicorp/yamux"
	"github.com/quic-go/quic-go"

	v1 "github.com/fatedier/frp/pkg/config/v1"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/naming"
	"github.com/fatedier/frp/pkg/nathole"
	"github.com/fatedier/frp/pkg/transport"
	netpkg "github.com/fatedier/frp/pkg/util/net"
)

func init() {
	RegisterProxyFactory(reflect.TypeFor[*v1.XTCPProxyConfig](), NewXTCPProxy)
}

type XTCPProxy struct {
	*BaseProxy

	cfg *v1.XTCPProxyConfig
}

func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
	unwrapped, ok := cfg.(*v1.XTCPProxyConfig)
	if !ok {
		return nil
	}
	return &XTCPProxy{
		BaseProxy: baseProxy,
		cfg:       unwrapped,
	}
}

func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkConn) {
	xl := pxy.xl
	defer conn.Close()
	var natHoleSidMsg msg.NatHoleSid
	err := msg.ReadMsgInto(conn, &natHoleSidMsg)
	if err != nil {
		xl.Errorf("xtcp read from workConn error: %v", err)
		return
	}

	xl.Tracef("nathole prepare start")

	// Prepare NAT traversal options
	var opts nathole.PrepareOptions
	if pxy.cfg.NatTraversal != nil && pxy.cfg.NatTraversal.DisableAssistedAddrs {
		opts.DisableAssistedAddrs = true
	}

	prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer}, opts)
	if err != nil {
		xl.Warnf("nathole prepare error: %v", err)
		return
	}

	xl.Infof("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
		prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
	defer prepareResult.ListenConn.Close()

	// send NatHoleClient msg to server
	transactionID := nathole.NewTransactionID()
	natHoleClientMsg := &msg.NatHoleClient{
		TransactionID: transactionID,
		ProxyName:     naming.AddUserPrefix(pxy.clientCfg.User, pxy.cfg.Name),
		Sid:           natHoleSidMsg.Sid,
		MappedAddrs:   prepareResult.Addrs,
		AssistedAddrs: prepareResult.AssistedAddrs,
	}

	xl.Tracef("nathole exchange info start")
	natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
	if err != nil {
		xl.Warnf("nathole exchange info error: %v", err)
		return
	}

	xl.Infof("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
		natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
		natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)

	listenConn := prepareResult.ListenConn
	newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey))
	if err != nil {
		listenConn.Close()
		xl.Warnf("make hole error: %v", err)
		_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
			Sid:     natHoleRespMsg.Sid,
			Success: false,
		})
		return
	}
	listenConn = newListenConn
	xl.Infof("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)

	_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
		Sid:     natHoleRespMsg.Sid,
		Success: true,
	})

	if natHoleRespMsg.Protocol == "kcp" {
		pxy.listenByKCP(listenConn, raddr, startWorkConnMsg)
		return
	}

	// default is quic
	pxy.listenByQUIC(listenConn, raddr, startWorkConnMsg)
}

func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
	xl := pxy.xl
	listenConn.Close()
	ladd
Download .txt
gitextract_d931dhl1/

├── .circleci/
│   └── config.yml
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   ├── config.yml
│   │   └── feature_request.yaml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── build-and-push-image.yml
│       ├── golangci-lint.yml
│       ├── goreleaser.yml
│       └── stale.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── AGENTS.md
├── LICENSE
├── Makefile
├── Makefile.cross-compiles
├── README.md
├── README_zh.md
├── Release.md
├── assets/
│   └── assets.go
├── client/
│   ├── api_router.go
│   ├── config_manager.go
│   ├── config_manager_test.go
│   ├── configmgmt/
│   │   └── types.go
│   ├── connector.go
│   ├── control.go
│   ├── event/
│   │   └── event.go
│   ├── health/
│   │   └── health.go
│   ├── http/
│   │   ├── controller.go
│   │   ├── controller_test.go
│   │   └── model/
│   │       ├── proxy_definition.go
│   │       ├── types.go
│   │       └── visitor_definition.go
│   ├── proxy/
│   │   ├── general_tcp.go
│   │   ├── proxy.go
│   │   ├── proxy_manager.go
│   │   ├── proxy_wrapper.go
│   │   ├── sudp.go
│   │   ├── udp.go
│   │   └── xtcp.go
│   ├── service.go
│   ├── service_test.go
│   └── visitor/
│       ├── stcp.go
│       ├── sudp.go
│       ├── visitor.go
│       ├── visitor_manager.go
│       └── xtcp.go
├── cmd/
│   ├── frpc/
│   │   ├── main.go
│   │   └── sub/
│   │       ├── admin.go
│   │       ├── nathole.go
│   │       ├── proxy.go
│   │       ├── root.go
│   │       └── verify.go
│   └── frps/
│       ├── main.go
│       ├── root.go
│       └── verify.go
├── conf/
│   ├── frpc.toml
│   ├── frpc_full_example.toml
│   ├── frps.toml
│   ├── frps_full_example.toml
│   └── legacy/
│       ├── frpc_legacy_full.ini
│       └── frps_legacy_full.ini
├── doc/
│   ├── server_plugin.md
│   ├── ssh_tunnel_gateway.md
│   └── virtual_net.md
├── dockerfiles/
│   ├── Dockerfile-for-frpc
│   └── Dockerfile-for-frps
├── go.mod
├── go.sum
├── hack/
│   ├── download.sh
│   └── run-e2e.sh
├── package.sh
├── pkg/
│   ├── auth/
│   │   ├── auth.go
│   │   ├── legacy/
│   │   │   └── legacy.go
│   │   ├── oidc.go
│   │   ├── oidc_test.go
│   │   ├── pass.go
│   │   └── token.go
│   ├── config/
│   │   ├── flags.go
│   │   ├── legacy/
│   │   │   ├── README.md
│   │   │   ├── client.go
│   │   │   ├── conversion.go
│   │   │   ├── parse.go
│   │   │   ├── proxy.go
│   │   │   ├── server.go
│   │   │   ├── utils.go
│   │   │   ├── value.go
│   │   │   └── visitor.go
│   │   ├── load.go
│   │   ├── load_test.go
│   │   ├── source/
│   │   │   ├── aggregator.go
│   │   │   ├── aggregator_test.go
│   │   │   ├── base_source.go
│   │   │   ├── base_source_test.go
│   │   │   ├── clone.go
│   │   │   ├── config_source.go
│   │   │   ├── config_source_test.go
│   │   │   ├── source.go
│   │   │   ├── store.go
│   │   │   └── store_test.go
│   │   ├── template.go
│   │   ├── types/
│   │   │   ├── types.go
│   │   │   └── types_test.go
│   │   └── v1/
│   │       ├── api.go
│   │       ├── client.go
│   │       ├── client_test.go
│   │       ├── clone_test.go
│   │       ├── common.go
│   │       ├── decode.go
│   │       ├── decode_test.go
│   │       ├── proxy.go
│   │       ├── proxy_plugin.go
│   │       ├── proxy_test.go
│   │       ├── server.go
│   │       ├── server_test.go
│   │       ├── store.go
│   │       ├── validation/
│   │       │   ├── client.go
│   │       │   ├── common.go
│   │       │   ├── oidc.go
│   │       │   ├── oidc_test.go
│   │       │   ├── plugin.go
│   │       │   ├── proxy.go
│   │       │   ├── server.go
│   │       │   ├── validation.go
│   │       │   ├── validator.go
│   │       │   └── visitor.go
│   │       ├── value_source.go
│   │       ├── value_source_test.go
│   │       ├── visitor.go
│   │       └── visitor_plugin.go
│   ├── errors/
│   │   └── errors.go
│   ├── metrics/
│   │   ├── aggregate/
│   │   │   └── server.go
│   │   ├── mem/
│   │   │   ├── server.go
│   │   │   └── types.go
│   │   ├── metrics.go
│   │   └── prometheus/
│   │       └── server.go
│   ├── msg/
│   │   ├── ctl.go
│   │   ├── handler.go
│   │   └── msg.go
│   ├── naming/
│   │   ├── names.go
│   │   └── names_test.go
│   ├── nathole/
│   │   ├── analysis.go
│   │   ├── classify.go
│   │   ├── controller.go
│   │   ├── discovery.go
│   │   ├── nathole.go
│   │   └── utils.go
│   ├── plugin/
│   │   ├── client/
│   │   │   ├── http2http.go
│   │   │   ├── http2https.go
│   │   │   ├── http_proxy.go
│   │   │   ├── https2http.go
│   │   │   ├── https2https.go
│   │   │   ├── plugin.go
│   │   │   ├── socks5.go
│   │   │   ├── static_file.go
│   │   │   ├── tls2raw.go
│   │   │   ├── unix_domain_socket.go
│   │   │   └── virtual_net.go
│   │   ├── server/
│   │   │   ├── http.go
│   │   │   ├── manager.go
│   │   │   ├── plugin.go
│   │   │   ├── tracer.go
│   │   │   └── types.go
│   │   └── visitor/
│   │       ├── plugin.go
│   │       └── virtual_net.go
│   ├── policy/
│   │   ├── featuregate/
│   │   │   └── feature_gate.go
│   │   └── security/
│   │       └── unsafe.go
│   ├── proto/
│   │   └── udp/
│   │       ├── udp.go
│   │       └── udp_test.go
│   ├── sdk/
│   │   └── client/
│   │       └── client.go
│   ├── ssh/
│   │   ├── gateway.go
│   │   ├── server.go
│   │   └── terminal.go
│   ├── transport/
│   │   ├── message.go
│   │   └── tls.go
│   ├── util/
│   │   ├── http/
│   │   │   ├── context.go
│   │   │   ├── error.go
│   │   │   ├── handler.go
│   │   │   ├── http.go
│   │   │   ├── middleware.go
│   │   │   └── server.go
│   │   ├── jsonx/
│   │   │   ├── json_v1.go
│   │   │   └── raw_message.go
│   │   ├── limit/
│   │   │   ├── reader.go
│   │   │   └── writer.go
│   │   ├── log/
│   │   │   └── log.go
│   │   ├── metric/
│   │   │   ├── counter.go
│   │   │   ├── counter_test.go
│   │   │   ├── date_counter.go
│   │   │   ├── date_counter_test.go
│   │   │   └── metrics.go
│   │   ├── net/
│   │   │   ├── conn.go
│   │   │   ├── dial.go
│   │   │   ├── dns.go
│   │   │   ├── http.go
│   │   │   ├── kcp.go
│   │   │   ├── listener.go
│   │   │   ├── proxyprotocol.go
│   │   │   ├── proxyprotocol_test.go
│   │   │   ├── tls.go
│   │   │   ├── udp.go
│   │   │   └── websocket.go
│   │   ├── system/
│   │   │   ├── system.go
│   │   │   └── system_android.go
│   │   ├── tcpmux/
│   │   │   └── httpconnect.go
│   │   ├── util/
│   │   │   ├── types.go
│   │   │   ├── util.go
│   │   │   └── util_test.go
│   │   ├── version/
│   │   │   └── version.go
│   │   ├── vhost/
│   │   │   ├── http.go
│   │   │   ├── https.go
│   │   │   ├── https_test.go
│   │   │   ├── resource.go
│   │   │   ├── router.go
│   │   │   └── vhost.go
│   │   ├── wait/
│   │   │   └── backoff.go
│   │   └── xlog/
│   │       ├── ctx.go
│   │       ├── log_writer.go
│   │       └── xlog.go
│   ├── virtual/
│   │   └── client.go
│   └── vnet/
│       ├── controller.go
│       ├── message.go
│       ├── tun.go
│       ├── tun_darwin.go
│       ├── tun_linux.go
│       └── tun_unsupported.go
├── server/
│   ├── api_router.go
│   ├── control.go
│   ├── controller/
│   │   └── resource.go
│   ├── group/
│   │   ├── base.go
│   │   ├── base_test.go
│   │   ├── group.go
│   │   ├── http.go
│   │   ├── https.go
│   │   ├── listener.go
│   │   ├── listener_test.go
│   │   ├── registry.go
│   │   ├── registry_test.go
│   │   ├── tcp.go
│   │   └── tcpmux.go
│   ├── http/
│   │   ├── controller.go
│   │   ├── controller_test.go
│   │   └── model/
│   │       └── types.go
│   ├── metrics/
│   │   └── metrics.go
│   ├── ports/
│   │   └── ports.go
│   ├── proxy/
│   │   ├── http.go
│   │   ├── https.go
│   │   ├── proxy.go
│   │   ├── stcp.go
│   │   ├── sudp.go
│   │   ├── tcp.go
│   │   ├── tcpmux.go
│   │   ├── udp.go
│   │   └── xtcp.go
│   ├── registry/
│   │   └── registry.go
│   ├── service.go
│   └── visitor/
│       └── visitor.go
├── test/
│   └── e2e/
│       ├── e2e.go
│       ├── e2e_test.go
│       ├── examples.go
│       ├── framework/
│       │   ├── cleanup.go
│       │   ├── client.go
│       │   ├── consts/
│       │   │   └── consts.go
│       │   ├── expect.go
│       │   ├── framework.go
│       │   ├── log.go
│       │   ├── mockservers.go
│       │   ├── process.go
│       │   ├── request.go
│       │   ├── test_context.go
│       │   └── util.go
│       ├── legacy/
│       │   ├── basic/
│       │   │   ├── basic.go
│       │   │   ├── client.go
│       │   │   ├── client_server.go
│       │   │   ├── cmd.go
│       │   │   ├── config.go
│       │   │   ├── http.go
│       │   │   ├── server.go
│       │   │   ├── tcpmux.go
│       │   │   └── xtcp.go
│       │   ├── features/
│       │   │   ├── bandwidth_limit.go
│       │   │   ├── chaos.go
│       │   │   ├── group.go
│       │   │   ├── heartbeat.go
│       │   │   ├── monitor.go
│       │   │   └── real_ip.go
│       │   └── plugin/
│       │       ├── client.go
│       │       └── server.go
│       ├── mock/
│       │   └── server/
│       │       ├── httpserver/
│       │       │   └── server.go
│       │       ├── interface.go
│       │       ├── oidcserver/
│       │       │   └── oidcserver.go
│       │       └── streamserver/
│       │           └── server.go
│       ├── pkg/
│       │   ├── cert/
│       │   │   ├── generator.go
│       │   │   └── selfsigned.go
│       │   ├── plugin/
│       │   │   └── plugin.go
│       │   ├── port/
│       │   │   ├── port.go
│       │   │   └── util.go
│       │   ├── process/
│       │   │   └── process.go
│       │   ├── request/
│       │   │   └── request.go
│       │   ├── rpc/
│       │   │   └── rpc.go
│       │   └── ssh/
│       │       └── client.go
│       ├── suites.go
│       └── v1/
│           ├── basic/
│           │   ├── annotations.go
│           │   ├── basic.go
│           │   ├── client.go
│           │   ├── client_server.go
│           │   ├── cmd.go
│           │   ├── config.go
│           │   ├── http.go
│           │   ├── oidc.go
│           │   ├── server.go
│           │   ├── tcpmux.go
│           │   ├── token_source.go
│           │   └── xtcp.go
│           ├── features/
│           │   ├── bandwidth_limit.go
│           │   ├── chaos.go
│           │   ├── group.go
│           │   ├── heartbeat.go
│           │   ├── monitor.go
│           │   ├── real_ip.go
│           │   ├── ssh_tunnel.go
│           │   └── store.go
│           └── plugin/
│               ├── client.go
│               └── server.go
└── web/
    ├── frpc/
    │   ├── .gitignore
    │   ├── .prettierrc.json
    │   ├── Makefile
    │   ├── README.md
    │   ├── auto-imports.d.ts
    │   ├── components.d.ts
    │   ├── embed.go
    │   ├── embed_stub.go
    │   ├── env.d.ts
    │   ├── eslint.config.js
    │   ├── index.html
    │   ├── package.json
    │   ├── src/
    │   │   ├── App.vue
    │   │   ├── api/
    │   │   │   ├── frpc.ts
    │   │   │   └── http.ts
    │   │   ├── assets/
    │   │   │   └── css/
    │   │   │       ├── _form-layout.scss
    │   │   │       ├── _index.scss
    │   │   │       ├── _mixins.scss
    │   │   │       ├── _variables.scss
    │   │   │       ├── dark.css
    │   │   │       └── var.css
    │   │   ├── components/
    │   │   │   ├── ConfigField.vue
    │   │   │   ├── ConfigSection.vue
    │   │   │   ├── KeyValueEditor.vue
    │   │   │   ├── ProxyCard.vue
    │   │   │   ├── StatusPills.vue
    │   │   │   ├── StringListEditor.vue
    │   │   │   ├── proxy-form/
    │   │   │   │   ├── ProxyAuthSection.vue
    │   │   │   │   ├── ProxyBackendSection.vue
    │   │   │   │   ├── ProxyBaseSection.vue
    │   │   │   │   ├── ProxyFormLayout.vue
    │   │   │   │   ├── ProxyHealthSection.vue
    │   │   │   │   ├── ProxyHttpSection.vue
    │   │   │   │   ├── ProxyLoadBalanceSection.vue
    │   │   │   │   ├── ProxyMetadataSection.vue
    │   │   │   │   ├── ProxyNatSection.vue
    │   │   │   │   ├── ProxyRemoteSection.vue
    │   │   │   │   └── ProxyTransportSection.vue
    │   │   │   └── visitor-form/
    │   │   │       ├── VisitorBaseSection.vue
    │   │   │       ├── VisitorConnectionSection.vue
    │   │   │       ├── VisitorFormLayout.vue
    │   │   │       ├── VisitorTransportSection.vue
    │   │   │       └── VisitorXtcpSection.vue
    │   │   ├── composables/
    │   │   │   └── useResponsive.ts
    │   │   ├── main.ts
    │   │   ├── router/
    │   │   │   └── index.ts
    │   │   ├── stores/
    │   │   │   ├── client.ts
    │   │   │   ├── proxy.ts
    │   │   │   └── visitor.ts
    │   │   ├── svg.d.ts
    │   │   ├── types/
    │   │   │   ├── constants.ts
    │   │   │   ├── index.ts
    │   │   │   ├── proxy-converters.ts
    │   │   │   ├── proxy-form.ts
    │   │   │   ├── proxy-status.ts
    │   │   │   └── proxy-store.ts
    │   │   ├── utils/
    │   │   │   └── format.ts
    │   │   └── views/
    │   │       ├── ClientConfigure.vue
    │   │       ├── ProxyDetail.vue
    │   │       ├── ProxyEdit.vue
    │   │       ├── ProxyList.vue
    │   │       ├── VisitorDetail.vue
    │   │       ├── VisitorEdit.vue
    │   │       └── VisitorList.vue
    │   ├── tsconfig.json
    │   ├── tsconfig.node.json
    │   └── vite.config.mts
    ├── frps/
    │   ├── .gitignore
    │   ├── .prettierrc.json
    │   ├── Makefile
    │   ├── README.md
    │   ├── auto-imports.d.ts
    │   ├── components.d.ts
    │   ├── embed.go
    │   ├── embed_stub.go
    │   ├── env.d.ts
    │   ├── eslint.config.js
    │   ├── index.html
    │   ├── package.json
    │   ├── src/
    │   │   ├── App.vue
    │   │   ├── api/
    │   │   │   ├── client.ts
    │   │   │   ├── http.ts
    │   │   │   ├── proxy.ts
    │   │   │   └── server.ts
    │   │   ├── assets/
    │   │   │   └── css/
    │   │   │       ├── custom.css
    │   │   │       ├── dark.css
    │   │   │       └── var.css
    │   │   ├── components/
    │   │   │   ├── ClientCard.vue
    │   │   │   ├── ProxyCard.vue
    │   │   │   ├── StatCard.vue
    │   │   │   └── Traffic.vue
    │   │   ├── composables/
    │   │   │   └── useResponsive.ts
    │   │   ├── main.ts
    │   │   ├── router/
    │   │   │   └── index.ts
    │   │   ├── svg.d.ts
    │   │   ├── types/
    │   │   │   ├── client.ts
    │   │   │   ├── proxy.ts
    │   │   │   └── server.ts
    │   │   ├── utils/
    │   │   │   ├── client.ts
    │   │   │   ├── format.ts
    │   │   │   └── proxy.ts
    │   │   └── views/
    │   │       ├── ClientDetail.vue
    │   │       ├── Clients.vue
    │   │       ├── Proxies.vue
    │   │       ├── ProxyDetail.vue
    │   │       └── ServerOverview.vue
    │   ├── tsconfig.json
    │   ├── tsconfig.node.json
    │   └── vite.config.mts
    ├── package.json
    └── shared/
        ├── components/
        │   ├── ActionButton.vue
        │   ├── BaseDialog.vue
        │   ├── ConfirmDialog.vue
        │   ├── FilterDropdown.vue
        │   ├── PopoverMenu.vue
        │   └── PopoverMenuItem.vue
        ├── css/
        │   ├── _index.scss
        │   ├── _mixins.scss
        │   └── _variables.scss
        └── package.json
Download .txt
SYMBOL INDEX (2183 symbols across 270 files)

FILE: assets/assets.go
  type emptyFS (line 32) | type emptyFS struct
    method Open (line 34) | func (emptyFS) Open(name string) (http.File, error) {
  function Load (line 40) | func Load(path string) {
  function Register (line 52) | func Register(fileSystem fs.FS) {

FILE: client/api_router.go
  method registerRouteHandlers (line 26) | func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegister...
  function healthz (line 66) | func healthz(w http.ResponseWriter, _ *http.Request) {
  function newAPIController (line 70) | func newAPIController(svr *Service) *adminapi.Controller {
  method getAllProxyStatus (line 79) | func (svr *Service) getAllProxyStatus() []*proxy.WorkingStatus {

FILE: client/config_manager.go
  type serviceConfigManager (line 18) | type serviceConfigManager struct
    method ReloadFromFile (line 26) | func (m *serviceConfigManager) ReloadFromFile(strict bool) error {
    method ReadConfigFile (line 56) | func (m *serviceConfigManager) ReadConfigFile() (string, error) {
    method WriteConfigFile (line 68) | func (m *serviceConfigManager) WriteConfigFile(content []byte) error {
    method GetProxyStatus (line 79) | func (m *serviceConfigManager) GetProxyStatus() []*proxy.WorkingStatus {
    method GetProxyConfig (line 83) | func (m *serviceConfigManager) GetProxyConfig(name string) (v1.ProxyCo...
    method GetVisitorConfig (line 104) | func (m *serviceConfigManager) GetVisitorConfig(name string) (v1.Visit...
    method IsStoreProxyEnabled (line 125) | func (m *serviceConfigManager) IsStoreProxyEnabled(name string) bool {
    method StoreEnabled (line 146) | func (m *serviceConfigManager) StoreEnabled() bool {
    method ListStoreProxies (line 153) | func (m *serviceConfigManager) ListStoreProxies() ([]v1.ProxyConfigure...
    method GetStoreProxy (line 161) | func (m *serviceConfigManager) GetStoreProxy(name string) (v1.ProxyCon...
    method CreateStoreProxy (line 178) | func (m *serviceConfigManager) CreateStoreProxy(cfg v1.ProxyConfigurer...
    method UpdateStoreProxy (line 200) | func (m *serviceConfigManager) UpdateStoreProxy(name string, cfg v1.Pr...
    method DeleteStoreProxy (line 232) | func (m *serviceConfigManager) DeleteStoreProxy(name string) error {
    method ListStoreVisitors (line 253) | func (m *serviceConfigManager) ListStoreVisitors() ([]v1.VisitorConfig...
    method GetStoreVisitor (line 261) | func (m *serviceConfigManager) GetStoreVisitor(name string) (v1.Visito...
    method CreateStoreVisitor (line 278) | func (m *serviceConfigManager) CreateStoreVisitor(cfg v1.VisitorConfig...
    method UpdateStoreVisitor (line 301) | func (m *serviceConfigManager) UpdateStoreVisitor(name string, cfg v1....
    method DeleteStoreVisitor (line 333) | func (m *serviceConfigManager) DeleteStoreVisitor(name string) error {
    method GracefulClose (line 354) | func (m *serviceConfigManager) GracefulClose(d time.Duration) {
    method storeSourceOrError (line 358) | func (m *serviceConfigManager) storeSourceOrError() (*source.StoreSour...
    method withStoreMutationAndReload (line 369) | func (m *serviceConfigManager) withStoreMutationAndReload(
    method withStoreProxyMutationAndReload (line 390) | func (m *serviceConfigManager) withStoreProxyMutationAndReload(
    method withStoreVisitorMutationAndReload (line 416) | func (m *serviceConfigManager) withStoreVisitorMutationAndReload(
    method validateStoreProxyConfigurer (line 442) | func (m *serviceConfigManager) validateStoreProxyConfigurer(cfg v1.Pro...
    method validateStoreVisitorConfigurer (line 454) | func (m *serviceConfigManager) validateStoreVisitorConfigurer(cfg v1.V...
  function newServiceConfigManager (line 22) | func newServiceConfigManager(svr *Service) configmgmt.ConfigManager {

FILE: client/config_manager_test.go
  function newTestRawTCPProxyConfig (line 13) | func newTestRawTCPProxyConfig(name string) *v1.TCPProxyConfig {
  function TestServiceConfigManagerCreateStoreProxyConflict (line 25) | func TestServiceConfigManagerCreateStoreProxyConflict(t *testing.T) {
  function TestServiceConfigManagerCreateStoreProxyKeepsStoreOnReloadFailure (line 57) | func TestServiceConfigManagerCreateStoreProxyKeepsStoreOnReloadFailure(t...
  function TestServiceConfigManagerCreateStoreProxyStoreDisabled (line 84) | func TestServiceConfigManagerCreateStoreProxyStoreDisabled(t *testing.T) {
  function TestServiceConfigManagerCreateStoreProxyDoesNotPersistRuntimeDefaults (line 100) | func TestServiceConfigManagerCreateStoreProxyDoesNotPersistRuntimeDefaul...

FILE: client/configmgmt/types.go
  type ConfigManager (line 19) | type ConfigManager interface

FILE: client/connector.go
  type Connector (line 38) | type Connector interface
  type defaultConnectorImpl (line 45) | type defaultConnectorImpl struct
    method Open (line 65) | func (c *defaultConnectorImpl) Open() error {
    method Connect (line 129) | func (c *defaultConnectorImpl) Connect() (net.Conn, error) {
    method realConnect (line 147) | func (c *defaultConnectorImpl) realConnect() (net.Conn, error) {
    method Close (line 217) | func (c *defaultConnectorImpl) Close() error {
  function NewConnector (line 54) | func NewConnector(ctx context.Context, cfg *v1.ClientCommonConfig) Conne...

FILE: client/control.go
  type SessionContext (line 36) | type SessionContext struct
  type Control (line 55) | type Control struct
    method Run (line 112) | func (ctl *Control) Run(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []...
    method SetInWorkConnCallback (line 122) | func (ctl *Control) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig,...
    method handleReqWorkConn (line 126) | func (ctl *Control) handleReqWorkConn(_ msg.Message) {
    method handleNewProxyResp (line 166) | func (ctl *Control) handleNewProxyResp(m msg.Message) {
    method handleNatHoleResp (line 180) | func (ctl *Control) handleNatHoleResp(m msg.Message) {
    method handlePong (line 191) | func (ctl *Control) handlePong(m msg.Message) {
    method closeSession (line 205) | func (ctl *Control) closeSession() {
    method Close (line 210) | func (ctl *Control) Close() error {
    method GracefulClose (line 214) | func (ctl *Control) GracefulClose(d time.Duration) error {
    method Done (line 225) | func (ctl *Control) Done() <-chan struct{} {
    method connectServer (line 230) | func (ctl *Control) connectServer() (net.Conn, error) {
    method registerMsgHandlers (line 234) | func (ctl *Control) registerMsgHandlers() {
    method heartbeatWorker (line 242) | func (ctl *Control) heartbeatWorker() {
    method worker (line 282) | func (ctl *Control) worker() {
    method UpdateAllConfigurer (line 296) | func (ctl *Control) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer...
  function NewControl (line 84) | func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Contr...

FILE: client/event/event.go
  type Handler (line 11) | type Handler
  type StartProxyPayload (line 13) | type StartProxyPayload struct
  type CloseProxyPayload (line 17) | type CloseProxyPayload struct

FILE: client/health/health.go
  type Monitor (line 33) | type Monitor struct
    method Start (line 97) | func (monitor *Monitor) Start() {
    method Stop (line 101) | func (monitor *Monitor) Stop() {
    method checkWorker (line 105) | func (monitor *Monitor) checkWorker() {
    method doCheck (line 141) | func (monitor *Monitor) doCheck(ctx context.Context) error {
    method doTCPCheck (line 152) | func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
    method doHTTPCheck (line 167) | func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
  function NewMonitor (line 54) | func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,

FILE: client/http/controller.go
  type Controller (line 35) | type Controller struct
    method toHTTPError (line 53) | func (c *Controller) toHTTPError(err error) error {
    method Reload (line 71) | func (c *Controller) Reload(ctx *httppkg.Context) (any, error) {
    method Stop (line 85) | func (c *Controller) Stop(ctx *httppkg.Context) (any, error) {
    method Status (line 91) | func (c *Controller) Status(ctx *httppkg.Context) (any, error) {
    method GetConfig (line 114) | func (c *Controller) GetConfig(ctx *httppkg.Context) (any, error) {
    method PutConfig (line 123) | func (c *Controller) PutConfig(ctx *httppkg.Context) (any, error) {
    method buildProxyStatusResp (line 139) | func (c *Controller) buildProxyStatusResp(status *proxy.WorkingStatus)...
    method GetProxyConfig (line 166) | func (c *Controller) GetProxyConfig(ctx *httppkg.Context) (any, error) {
    method GetVisitorConfig (line 185) | func (c *Controller) GetVisitorConfig(ctx *httppkg.Context) (any, erro...
    method ListStoreProxies (line 203) | func (c *Controller) ListStoreProxies(ctx *httppkg.Context) (any, erro...
    method GetStoreProxy (line 223) | func (c *Controller) GetStoreProxy(ctx *httppkg.Context) (any, error) {
    method CreateStoreProxy (line 242) | func (c *Controller) CreateStoreProxy(ctx *httppkg.Context) (any, erro...
    method UpdateStoreProxy (line 272) | func (c *Controller) UpdateStoreProxy(ctx *httppkg.Context) (any, erro...
    method DeleteStoreProxy (line 307) | func (c *Controller) DeleteStoreProxy(ctx *httppkg.Context) (any, erro...
    method ListStoreVisitors (line 319) | func (c *Controller) ListStoreVisitors(ctx *httppkg.Context) (any, err...
    method GetStoreVisitor (line 339) | func (c *Controller) GetStoreVisitor(ctx *httppkg.Context) (any, error) {
    method CreateStoreVisitor (line 358) | func (c *Controller) CreateStoreVisitor(ctx *httppkg.Context) (any, er...
    method UpdateStoreVisitor (line 388) | func (c *Controller) UpdateStoreVisitor(ctx *httppkg.Context) (any, er...
    method DeleteStoreVisitor (line 423) | func (c *Controller) DeleteStoreVisitor(ctx *httppkg.Context) (any, er...
  type ControllerParams (line 41) | type ControllerParams struct
  function NewController (line 46) | func NewController(params ControllerParams) *Controller {

FILE: client/http/controller_test.go
  type fakeConfigManager (line 22) | type fakeConfigManager struct
    method ReloadFromFile (line 45) | func (m *fakeConfigManager) ReloadFromFile(strict bool) error {
    method ReadConfigFile (line 52) | func (m *fakeConfigManager) ReadConfigFile() (string, error) {
    method WriteConfigFile (line 59) | func (m *fakeConfigManager) WriteConfigFile(content []byte) error {
    method GetProxyStatus (line 66) | func (m *fakeConfigManager) GetProxyStatus() []*proxy.WorkingStatus {
    method IsStoreProxyEnabled (line 73) | func (m *fakeConfigManager) IsStoreProxyEnabled(name string) bool {
    method StoreEnabled (line 80) | func (m *fakeConfigManager) StoreEnabled() bool {
    method GetProxyConfig (line 87) | func (m *fakeConfigManager) GetProxyConfig(name string) (v1.ProxyConfi...
    method GetVisitorConfig (line 94) | func (m *fakeConfigManager) GetVisitorConfig(name string) (v1.VisitorC...
    method ListStoreProxies (line 101) | func (m *fakeConfigManager) ListStoreProxies() ([]v1.ProxyConfigurer, ...
    method GetStoreProxy (line 108) | func (m *fakeConfigManager) GetStoreProxy(name string) (v1.ProxyConfig...
    method CreateStoreProxy (line 115) | func (m *fakeConfigManager) CreateStoreProxy(cfg v1.ProxyConfigurer) (...
    method UpdateStoreProxy (line 122) | func (m *fakeConfigManager) UpdateStoreProxy(name string, cfg v1.Proxy...
    method DeleteStoreProxy (line 129) | func (m *fakeConfigManager) DeleteStoreProxy(name string) error {
    method ListStoreVisitors (line 136) | func (m *fakeConfigManager) ListStoreVisitors() ([]v1.VisitorConfigure...
    method GetStoreVisitor (line 143) | func (m *fakeConfigManager) GetStoreVisitor(name string) (v1.VisitorCo...
    method CreateStoreVisitor (line 150) | func (m *fakeConfigManager) CreateStoreVisitor(cfg v1.VisitorConfigure...
    method UpdateStoreVisitor (line 157) | func (m *fakeConfigManager) UpdateStoreVisitor(name string, cfg v1.Vis...
    method DeleteStoreVisitor (line 164) | func (m *fakeConfigManager) DeleteStoreVisitor(name string) error {
    method GracefulClose (line 171) | func (m *fakeConfigManager) GracefulClose(d time.Duration) {
  function newRawTCPProxyConfig (line 177) | func newRawTCPProxyConfig(name string) *v1.TCPProxyConfig {
  function TestBuildProxyStatusRespStoreSourceEnabled (line 189) | func TestBuildProxyStatusRespStoreSourceEnabled(t *testing.T) {
  function TestReloadErrorMapping (line 216) | func TestReloadErrorMapping(t *testing.T) {
  function TestStoreProxyErrorMapping (line 241) | func TestStoreProxyErrorMapping(t *testing.T) {
  function TestStoreVisitorErrorMapping (line 276) | func TestStoreVisitorErrorMapping(t *testing.T) {
  function TestCreateStoreProxyIgnoresUnknownFields (line 297) | func TestCreateStoreProxyIgnoresUnknownFields(t *testing.T) {
  function TestCreateStoreVisitorIgnoresUnknownFields (line 329) | func TestCreateStoreVisitorIgnoresUnknownFields(t *testing.T) {
  function TestCreateStoreProxyPluginUnknownFieldsAreIgnored (line 364) | func TestCreateStoreProxyPluginUnknownFieldsAreIgnored(t *testing.T) {
  function TestCreateStoreVisitorPluginUnknownFieldsAreIgnored (line 400) | func TestCreateStoreVisitorPluginUnknownFieldsAreIgnored(t *testing.T) {
  function TestUpdateStoreProxyRejectsMismatchedTypeBlock (line 439) | func TestUpdateStoreProxyRejectsMismatchedTypeBlock(t *testing.T) {
  function TestUpdateStoreProxyRejectsNameMismatch (line 453) | func TestUpdateStoreProxyRejectsNameMismatch(t *testing.T) {
  function TestListStoreProxiesReturnsSortedPayload (line 467) | func TestListStoreProxiesReturnsSortedPayload(t *testing.T) {
  function fmtError (line 495) | func fmtError(sentinel error, msg string) error {
  function assertHTTPCode (line 499) | func assertHTTPCode(t *testing.T, err error, expected int) {
  function TestUpdateStoreProxyReturnsTypedPayload (line 510) | func TestUpdateStoreProxyReturnsTypedPayload(t *testing.T) {
  function TestGetProxyConfigFromManager (line 549) | func TestGetProxyConfigFromManager(t *testing.T) {
  function TestGetProxyConfigNotFound (line 587) | func TestGetProxyConfigNotFound(t *testing.T) {
  function TestGetVisitorConfigFromManager (line 607) | func TestGetVisitorConfigFromManager(t *testing.T) {
  function TestGetVisitorConfigNotFound (line 644) | func TestGetVisitorConfigNotFound(t *testing.T) {

FILE: client/http/model/proxy_definition.go
  type ProxyDefinition (line 10) | type ProxyDefinition struct
    method Validate (line 24) | func (p *ProxyDefinition) Validate(pathName string, isUpdate bool) err...
    method ToConfigurer (line 45) | func (p *ProxyDefinition) ToConfigurer() (v1.ProxyConfigurer, error) {
    method activeBlock (line 92) | func (p *ProxyDefinition) activeBlock() (v1.ProxyConfigurer, string, i...
  function ProxyDefinitionFromConfigurer (line 57) | func ProxyDefinitionFromConfigurer(cfg v1.ProxyConfigurer) (ProxyDefinit...
  function IsProxyType (line 141) | func IsProxyType(typ string) bool {

FILE: client/http/model/types.go
  constant SourceStore (line 17) | SourceStore = "store"
  type StatusResp (line 20) | type StatusResp
  type ProxyStatusResp (line 23) | type ProxyStatusResp struct
  type ProxyListResp (line 35) | type ProxyListResp struct
  type VisitorListResp (line 40) | type VisitorListResp struct

FILE: client/http/model/visitor_definition.go
  type VisitorDefinition (line 10) | type VisitorDefinition struct
    method Validate (line 19) | func (p *VisitorDefinition) Validate(pathName string, isUpdate bool) e...
    method ToConfigurer (line 40) | func (p *VisitorDefinition) ToConfigurer() (v1.VisitorConfigurer, erro...
    method activeBlock (line 77) | func (p *VisitorDefinition) activeBlock() (v1.VisitorConfigurer, strin...
  function VisitorDefinitionFromConfigurer (line 52) | func VisitorDefinitionFromConfigurer(cfg v1.VisitorConfigurer) (VisitorD...
  function IsVisitorType (line 100) | func IsVisitorType(typ string) bool {

FILE: client/proxy/general_tcp.go
  function init (line 23) | func init() {
  type GeneralTCPProxy (line 39) | type GeneralTCPProxy struct
  function NewGeneralTCPProxy (line 43) | func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy {

FILE: client/proxy/proxy.go
  function RegisterProxyFactory (line 44) | func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*Base...
  type Proxy (line 49) | type Proxy interface
  function NewProxy (line 57) | func NewProxy(
  type BaseProxy (line 89) | type BaseProxy struct
    method Run (line 106) | func (pxy *BaseProxy) Run() error {
    method Close (line 120) | func (pxy *BaseProxy) Close() {
    method wrapWorkConn (line 131) | func (pxy *BaseProxy) wrapWorkConn(conn net.Conn, encKey []byte) (io.R...
    method SetInWorkConnCallback (line 153) | func (pxy *BaseProxy) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfi...
    method InWorkConn (line 157) | func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
    method HandleTCPWorkConnection (line 167) | func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *ms...

FILE: client/proxy/proxy_manager.go
  type Manager (line 34) | type Manager struct
    method StartProxy (line 67) | func (pm *Manager) StartProxy(name string, remoteAddr string, serverRe...
    method SetInWorkConnCallback (line 82) | func (pm *Manager) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, ...
    method Close (line 86) | func (pm *Manager) Close() {
    method HandleWorkConn (line 95) | func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *m...
    method HandleEvent (line 106) | func (pm *Manager) HandleEvent(payload any) error {
    method GetAllProxyStatus (line 120) | func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
    method GetProxyStatus (line 130) | func (pm *Manager) GetProxyStatus(name string) (*WorkingStatus, bool) {
    method UpdateAll (line 139) | func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
  function NewManager (line 49) | func NewManager(

FILE: client/proxy/proxy_wrapper.go
  constant ProxyPhaseNew (line 39) | ProxyPhaseNew         = "new"
  constant ProxyPhaseWaitStart (line 40) | ProxyPhaseWaitStart   = "wait start"
  constant ProxyPhaseStartErr (line 41) | ProxyPhaseStartErr    = "start error"
  constant ProxyPhaseRunning (line 42) | ProxyPhaseRunning     = "running"
  constant ProxyPhaseCheckFailed (line 43) | ProxyPhaseCheckFailed = "check failed"
  constant ProxyPhaseClosed (line 44) | ProxyPhaseClosed      = "closed"
  type WorkingStatus (line 53) | type WorkingStatus struct
  type Wrapper (line 64) | type Wrapper struct
    method SetInWorkConnCallback (line 134) | func (pw *Wrapper) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, ...
    method SetRunningStatus (line 138) | func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string)...
    method Start (line 166) | func (pw *Wrapper) Start() {
    method Stop (line 173) | func (pw *Wrapper) Stop() {
    method close (line 186) | func (pw *Wrapper) close() {
    method checkWorker (line 194) | func (pw *Wrapper) checkWorker() {
    method statusNormalCallback (line 241) | func (pw *Wrapper) statusNormalCallback() {
    method statusFailedCallback (line 253) | func (pw *Wrapper) statusFailedCallback() {
    method InWorkConn (line 265) | func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
    method GetStatus (line 278) | func (pw *Wrapper) GetStatus() *WorkingStatus {
  function NewWrapper (line 94) | func NewWrapper(

FILE: client/proxy/sudp.go
  function init (line 34) | func init() {
  type SUDPProxy (line 38) | type SUDPProxy struct
    method Run (line 60) | func (pxy *SUDPProxy) Run() (err error) {
    method Close (line 68) | func (pxy *SUDPProxy) Close() {
    method InWorkConn (line 79) | func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
  function NewSUDPProxy (line 48) | func NewSUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {

FILE: client/proxy/udp.go
  function init (line 33) | func init() {
  type UDPProxy (line 37) | type UDPProxy struct
    method Run (line 62) | func (pxy *UDPProxy) Run() (err error) {
    method Close (line 70) | func (pxy *UDPProxy) Close() {
    method InWorkConn (line 88) | func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
  function NewUDPProxy (line 51) | func NewUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {

FILE: client/proxy/xtcp.go
  function init (line 36) | func init() {
  type XTCPProxy (line 40) | type XTCPProxy struct
    method InWorkConn (line 57) | func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg....
    method listenByKCP (line 134) | func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net....
    method listenByQUIC (line 172) | func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDP...
  function NewXTCPProxy (line 46) | func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {

FILE: client/service.go
  function init (line 47) | func init() {
  type cancelErr (line 57) | type cancelErr struct
    method Error (line 61) | func (e cancelErr) Error() string {
  type ServiceOptions (line 66) | type ServiceOptions struct
  function setServiceOptionsDefault (line 99) | func setServiceOptionsDefault(options *ServiceOptions) error {
  type Service (line 112) | type Service struct
    method Run (line 224) | func (svr *Service) Run(ctx context.Context) error {
    method keepControllerWorking (line 275) | func (svr *Service) keepControllerWorking() {
    method login (line 309) | func (svr *Service) login() (conn net.Conn, connector Connector, err e...
    method loopLoginUntilSuccess (line 374) | func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, f...
    method UpdateAllConfigurer (line 433) | func (svr *Service) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer...
    method UpdateConfigSource (line 449) | func (svr *Service) UpdateConfigSource(
    method Close (line 478) | func (svr *Service) Close() {
    method GracefulClose (line 482) | func (svr *Service) GracefulClose(d time.Duration) {
    method stop (line 487) | func (svr *Service) stop() {
    method getProxyStatus (line 513) | func (svr *Service) getProxyStatus(name string) (*proxy.WorkingStatus,...
    method getVisitorCfg (line 524) | func (svr *Service) getVisitorCfg(name string) (v1.VisitorConfigurer, ...
    method StatusExporter (line 535) | func (svr *Service) StatusExporter() StatusExporter {
    method reloadConfigFromSources (line 553) | func (svr *Service) reloadConfigFromSources() error {
    method reloadConfigFromSourcesLocked (line 559) | func (svr *Service) reloadConfigFromSourcesLocked() error {
  function NewService (line 161) | func NewService(options ServiceOptions) (*Service, error) {
  type StatusExporter (line 541) | type StatusExporter interface
  type statusExporterImpl (line 545) | type statusExporterImpl struct
    method GetProxyStatus (line 549) | func (s *statusExporterImpl) GetProxyStatus(name string) (*proxy.Worki...

FILE: client/service_test.go
  type failingConnector (line 18) | type failingConnector struct
    method Open (line 22) | func (c *failingConnector) Open() error {
    method Connect (line 26) | func (c *failingConnector) Connect() (net.Conn, error) {
    method Close (line 30) | func (c *failingConnector) Close() error {
  function getFreeTCPPort (line 34) | func getFreeTCPPort(t *testing.T) int {
  function TestRunStopsStartedComponentsOnInitialLoginFailure (line 46) | func TestRunStopsStartedComponentsOnInitialLoginFailure(t *testing.T) {
  function TestNewServiceDoesNotLeakAdminListenerOnAuthBuildFailure (line 85) | func TestNewServiceDoesNotLeakAdminListenerOnAuthBuildFailure(t *testing...
  function TestUpdateConfigSourceRollsBackReloadCommonOnReplaceAllFailure (line 118) | func TestUpdateConfigSourceRollsBackReloadCommonOnReplaceAllFailure(t *t...
  function TestUpdateConfigSourceKeepsReloadCommonOnReloadFailure (line 142) | func TestUpdateConfigSourceKeepsReloadCommonOnReloadFailure(t *testing.T) {
  function TestReloadConfigFromSourcesDoesNotMutateStoreConfigs (line 174) | func TestReloadConfigFromSourcesDoesNotMutateStoreConfigs(t *testing.T) {

FILE: client/visitor/stcp.go
  type STCPVisitor (line 27) | type STCPVisitor struct
    method Run (line 33) | func (sv *STCPVisitor) Run() (err error) {
    method Close (line 50) | func (sv *STCPVisitor) Close() {
    method handleConn (line 54) | func (sv *STCPVisitor) handleConn(userConn net.Conn) {

FILE: client/visitor/sudp.go
  type SUDPVisitor (line 33) | type SUDPVisitor struct
    method Run (line 46) | func (sv *SUDPVisitor) Run() (err error) {
    method dispatcher (line 70) | func (sv *SUDPVisitor) dispatcher() {
    method worker (line 113) | func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPP...
    method getNewVisitorConn (line 201) | func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, func(), error) {
    method Close (line 214) | func (sv *SUDPVisitor) Close() {

FILE: client/visitor/visitor.go
  type Helper (line 39) | type Helper interface
  type Visitor (line 54) | type Visitor interface
  function NewVisitor (line 60) | func NewVisitor(
  type BaseVisitor (line 115) | type BaseVisitor struct
    method AcceptConn (line 126) | func (v *BaseVisitor) AcceptConn(conn net.Conn) error {
    method acceptLoop (line 130) | func (v *BaseVisitor) acceptLoop(l net.Listener, name string, handleCo...
    method Close (line 142) | func (v *BaseVisitor) Close() {
    method dialRawVisitorConn (line 154) | func (v *BaseVisitor) dialRawVisitorConn(cfg *v1.VisitorBaseConfig) (n...
  function wrapVisitorConn (line 192) | func wrapVisitorConn(conn io.ReadWriteCloser, cfg *v1.VisitorBaseConfig)...

FILE: client/visitor/visitor_manager.go
  type Manager (line 33) | type Manager struct
    method keepVisitorsRunning (line 76) | func (vm *Manager) keepVisitorsRunning() {
    method Close (line 101) | func (vm *Manager) Close() {
    method startVisitor (line 115) | func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
    method UpdateAll (line 133) | func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) {
    method TransferConn (line 184) | func (vm *Manager) TransferConn(name string, conn net.Conn) error {
    method GetVisitorCfg (line 194) | func (vm *Manager) GetVisitorCfg(name string) (v1.VisitorConfigurer, b...
  function NewManager (line 48) | func NewManager(
  type visitorHelperImpl (line 201) | type visitorHelperImpl struct
    method ConnectServer (line 209) | func (v *visitorHelperImpl) ConnectServer() (net.Conn, error) {
    method TransferConn (line 213) | func (v *visitorHelperImpl) TransferConn(name string, conn net.Conn) e...
    method MsgTransporter (line 217) | func (v *visitorHelperImpl) MsgTransporter() transport.MessageTranspor...
    method VNetController (line 221) | func (v *visitorHelperImpl) VNetController() *vnet.Controller {
    method RunID (line 225) | func (v *visitorHelperImpl) RunID() string {

FILE: client/visitor/xtcp.go
  type XTCPVisitor (line 44) | type XTCPVisitor struct
    method Run (line 54) | func (sv *XTCPVisitor) Run() (err error) {
    method Close (line 84) | func (sv *XTCPVisitor) Close() {
    method processTunnelStartEvents (line 96) | func (sv *XTCPVisitor) processTunnelStartEvents() {
    method keepTunnelOpenWorker (line 113) | func (sv *XTCPVisitor) keepTunnelOpenWorker() {
    method handleConn (line 139) | func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
    method openTunnel (line 202) | func (sv *XTCPVisitor) openTunnel(ctx context.Context) (conn net.Conn,...
    method getTunnelConn (line 233) | func (sv *XTCPVisitor) getTunnelConn(ctx context.Context) (net.Conn, e...
    method makeNatHole (line 252) | func (sv *XTCPVisitor) makeNatHole() {
  type TunnelSession (line 321) | type TunnelSession interface
  type KCPTunnelSession (line 327) | type KCPTunnelSession struct
    method Init (line 337) | func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.U...
    method OpenConn (line 366) | func (ks *KCPTunnelSession) OpenConn(_ context.Context) (net.Conn, err...
    method Close (line 376) | func (ks *KCPTunnelSession) Close() {
  function NewKCPTunnelSession (line 333) | func NewKCPTunnelSession() TunnelSession {
  type QUICTunnelSession (line 389) | type QUICTunnelSession struct
    method Init (line 403) | func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net....
    method OpenConn (line 425) | func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, ...
    method Close (line 439) | func (qs *QUICTunnelSession) Close() {
  function NewQUICTunnelSession (line 397) | func NewQUICTunnelSession(clientCfg *v1.ClientCommonConfig) TunnelSession {

FILE: cmd/frpc/main.go
  function main (line 23) | func main() {

FILE: cmd/frpc/sub/admin.go
  function init (line 34) | func init() {
  function NewAdminCommand (line 52) | func NewAdminCommand(name, short string, handler func(*v1.ClientCommonCo...
  function ReloadHandler (line 75) | func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
  function StatusHandler (line 87) | func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
  function StopHandler (line 115) | func StopHandler(clientCfg *v1.ClientCommonConfig) error {

FILE: cmd/frpc/sub/nathole.go
  function init (line 33) | func init() {
  function validateForNatHoleDiscovery (line 95) | func validateForNatHoleDiscovery(cfg *v1.ClientCommonConfig) error {

FILE: cmd/frpc/sub/proxy.go
  function init (line 48) | func init() {
  function NewProxyCommand (line 73) | func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.Cl...
  function NewVisitorCommand (line 106) | func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v...
  function startService (line 138) | func startService(

FILE: cmd/frpc/sub/root.go
  function init (line 50) | func init() {
  function runMultipleClients (line 88) | func runMultipleClients(cfgDir string, unsafeFeatures *security.UnsafeFe...
  function Execute (line 109) | func Execute() {
  function handleTermSignal (line 116) | func handleTermSignal(svr *client.Service) {
  function runClient (line 123) | func runClient(cfgFilePath string, unsafeFeatures *security.UnsafeFeatur...
  function runClientWithAggregator (line 144) | func runClientWithAggregator(result *config.ClientConfigLoadResult, unsa...
  function startServiceWithAggregator (line 192) | func startServiceWithAggregator(

FILE: cmd/frpc/sub/verify.go
  function init (line 28) | func init() {

FILE: cmd/frps/main.go
  function main (line 23) | func main() {

FILE: cmd/frps/root.go
  function init (line 43) | func init() {
  function Execute (line 104) | func Execute() {
  function runServer (line 111) | func runServer(cfg *v1.ServerConfig) (err error) {

FILE: cmd/frps/verify.go
  function init (line 28) | func init() {

FILE: pkg/auth/auth.go
  type Setter (line 25) | type Setter interface
  type ClientAuth (line 31) | type ClientAuth struct
    method EncryptionKey (line 36) | func (a *ClientAuth) EncryptionKey() []byte {
  function BuildClientAuth (line 42) | func BuildClientAuth(cfg *v1.AuthClientConfig) (*ClientAuth, error) {
  function NewAuthSetter (line 64) | func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter, err er...
  type Verifier (line 83) | type Verifier interface
  type ServerAuth (line 89) | type ServerAuth struct
    method EncryptionKey (line 94) | func (a *ServerAuth) EncryptionKey() []byte {
  function BuildServerAuth (line 100) | func BuildServerAuth(cfg *v1.AuthServerConfig) (*ServerAuth, error) {
  function NewAuthVerifier (line 118) | func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {

FILE: pkg/auth/legacy/legacy.go
  type BaseConfig (line 17) | type BaseConfig struct
  function getDefaultBaseConf (line 31) | func getDefaultBaseConf() BaseConfig {
  type ClientConfig (line 39) | type ClientConfig struct
  function GetDefaultClientConf (line 45) | func GetDefaultClientConf() ClientConfig {
  type ServerConfig (line 53) | type ServerConfig struct
  function GetDefaultServerConf (line 59) | func GetDefaultServerConf() ServerConfig {
  type OidcClientConfig (line 67) | type OidcClientConfig struct
  function getDefaultOidcClientConf (line 93) | func getDefaultOidcClientConf() OidcClientConfig {
  type OidcServerConfig (line 104) | type OidcServerConfig struct
  function getDefaultOidcServerConf (line 125) | func getDefaultOidcServerConf() OidcServerConfig {
  type TokenConfig (line 134) | type TokenConfig struct
  function getDefaultTokenConf (line 141) | func getDefaultTokenConf() TokenConfig {

FILE: pkg/auth/oidc.go
  function createOIDCHTTPClient (line 38) | func createOIDCHTTPClient(trustedCAFile string, insecureSkipVerify bool,...
  type nonCachingTokenSource (line 83) | type nonCachingTokenSource struct
    method Token (line 88) | func (s *nonCachingTokenSource) Token() (*oauth2.Token, error) {
  type oidcTokenSource (line 98) | type oidcTokenSource struct
    method Token (line 106) | func (s *oidcTokenSource) Token() (*oauth2.Token, error) {
  type OidcAuthProvider (line 126) | type OidcAuthProvider struct
    method generateAccessToken (line 188) | func (auth *OidcAuthProvider) generateAccessToken() (accessToken strin...
    method SetLogin (line 196) | func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
    method SetPing (line 201) | func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
    method SetNewWorkConn (line 210) | func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWo...
  function NewOidcAuthSetter (line 132) | func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthO...
  type OidcTokenSourceAuthProvider (line 219) | type OidcTokenSourceAuthProvider struct
    method generateAccessToken (line 232) | func (auth *OidcTokenSourceAuthProvider) generateAccessToken() (access...
    method SetLogin (line 241) | func (auth *OidcTokenSourceAuthProvider) SetLogin(loginMsg *msg.Login)...
    method SetPing (line 246) | func (auth *OidcTokenSourceAuthProvider) SetPing(pingMsg *msg.Ping) (e...
    method SetNewWorkConn (line 255) | func (auth *OidcTokenSourceAuthProvider) SetNewWorkConn(newWorkConnMsg...
  function NewOidcTokenSourceAuthSetter (line 225) | func NewOidcTokenSourceAuthSetter(additionalAuthScopes []v1.AuthScope, v...
  type TokenVerifier (line 264) | type TokenVerifier interface
  type OidcAuthConsumer (line 268) | type OidcAuthConsumer struct
    method VerifyLogin (line 298) | func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err er...
    method verifyPostLoginToken (line 309) | func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string...
    method VerifyPing (line 325) | func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
    method VerifyNewWorkConn (line 333) | func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.Ne...
  function NewTokenVerifier (line 276) | func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
  function NewOidcAuthVerifier (line 290) | func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier T...

FILE: pkg/auth/oidc_test.go
  type mockTokenVerifier (line 20) | type mockTokenVerifier struct
    method Verify (line 22) | func (m *mockTokenVerifier) Verify(ctx context.Context, subject string...
  function TestPingWithEmptySubjectFromLoginFails (line 28) | func TestPingWithEmptySubjectFromLoginFails(t *testing.T) {
  function TestPingAfterLoginWithNewSubjectSucceeds (line 39) | func TestPingAfterLoginWithNewSubjectSucceeds(t *testing.T) {
  function TestPingAfterLoginWithDifferentSubjectFails (line 54) | func TestPingAfterLoginWithDifferentSubjectFails(t *testing.T) {
  function TestOidcAuthProviderFallsBackWhenNoExpiry (line 70) | func TestOidcAuthProviderFallsBackWhenNoExpiry(t *testing.T) {
  function TestOidcAuthProviderCachesToken (line 114) | func TestOidcAuthProviderCachesToken(t *testing.T) {
  function TestOidcAuthProviderRetriesOnInitialFailure (line 159) | func TestOidcAuthProviderRetriesOnInitialFailure(t *testing.T) {
  function TestNewOidcAuthSetterRejectsInvalidStaticConfig (line 217) | func TestNewOidcAuthSetterRejectsInvalidStaticConfig(t *testing.T) {

FILE: pkg/auth/pass.go
  type alwaysPass (line 25) | type alwaysPass struct
    method VerifyLogin (line 27) | func (*alwaysPass) VerifyLogin(*msg.Login) error { return nil }
    method VerifyPing (line 29) | func (*alwaysPass) VerifyPing(*msg.Ping) error { return nil }
    method VerifyNewWorkConn (line 31) | func (*alwaysPass) VerifyNewWorkConn(*msg.NewWorkConn) error { return ...

FILE: pkg/auth/token.go
  type TokenAuthSetterVerifier (line 27) | type TokenAuthSetterVerifier struct
    method SetLogin (line 39) | func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) err...
    method SetPing (line 44) | func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
    method SetNewWorkConn (line 54) | func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *ms...
    method VerifyLogin (line 64) | func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
    method VerifyPing (line 71) | func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
    method VerifyNewWorkConn (line 82) | func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkC...
  function NewTokenAuth (line 32) | func NewTokenAuth(additionalAuthScopes []v1.AuthScope, token string) *To...

FILE: pkg/config/flags.go
  function WordSepNormalizeFunc (line 31) | func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.Normalize...
  type RegisterFlagOption (line 38) | type RegisterFlagOption
  type registerFlagOptions (line 40) | type registerFlagOptions struct
  function WithSSHMode (line 44) | func WithSSHMode() RegisterFlagOption {
  type BandwidthQuantityFlag (line 50) | type BandwidthQuantityFlag struct
    method Set (line 54) | func (f *BandwidthQuantityFlag) Set(s string) error {
    method String (line 58) | func (f *BandwidthQuantityFlag) String() string {
    method Type (line 62) | func (f *BandwidthQuantityFlag) Type() string {
  function RegisterProxyFlags (line 66) | func RegisterProxyFlags(cmd *cobra.Command, c v1.ProxyConfigurer, opts ....
  function registerProxyBaseConfigFlags (line 99) | func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseCon...
  function registerProxyDomainConfigFlags (line 122) | func registerProxyDomainConfigFlags(cmd *cobra.Command, c *v1.DomainConf...
  function RegisterVisitorFlags (line 130) | func RegisterVisitorFlags(cmd *cobra.Command, c v1.VisitorConfigurer, op...
  function registerVisitorBaseConfigFlags (line 136) | func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBas...
  function RegisterClientCommonConfigFlags (line 150) | func RegisterClientCommonConfigFlags(cmd *cobra.Command, c *v1.ClientCom...
  type PortsRangeSliceFlag (line 174) | type PortsRangeSliceFlag struct
    method String (line 178) | func (f *PortsRangeSliceFlag) String() string {
    method Set (line 185) | func (f *PortsRangeSliceFlag) Set(s string) error {
    method Type (line 194) | func (f *PortsRangeSliceFlag) Type() string {
  type BoolFuncFlag (line 198) | type BoolFuncFlag struct
    method String (line 205) | func (f *BoolFuncFlag) String() string {
    method Set (line 209) | func (f *BoolFuncFlag) Set(s string) error {
    method Type (line 225) | func (f *BoolFuncFlag) Type() string {
  function RegisterServerConfigFlags (line 229) | func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig, o...

FILE: pkg/config/legacy/client.go
  type ClientCommonConf (line 34) | type ClientCommonConf struct
    method Validate (line 362) | func (cfg *ClientCommonConf) Validate() error {
  function UnmarshalClientConfFromIni (line 173) | func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
  function LoadAllProxyConfsFromIni (line 204) | func LoadAllProxyConfsFromIni(
  function renderRangeProxyTemplates (line 289) | func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {
  function copySection (line 338) | func copySection(source, target *ini.Section) {
  function GetDefaultClientConf (line 348) | func GetDefaultClientConf() ClientCommonConf {

FILE: pkg/config/legacy/conversion.go
  function Convert_ClientCommonConf_To_v1 (line 26) | func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCo...
  function Convert_ServerCommonConf_To_v1 (line 88) | func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerCo...
  function transformHeadersFromPluginParams (line 171) | func transformHeadersFromPluginParams(params map[string]string) v1.Heade...
  function Convert_ProxyConf_To_v1_Base (line 186) | func Convert_ProxyConf_To_v1_Base(conf ProxyConf) *v1.ProxyBaseConfig {
  function Convert_ProxyConf_To_v1 (line 261) | func Convert_ProxyConf_To_v1(conf ProxyConf) v1.ProxyConfigurer {
  function Convert_VisitorConf_To_v1_Base (line 317) | func Convert_VisitorConf_To_v1_Base(conf VisitorConf) *v1.VisitorBaseCon...
  function Convert_VisitorConf_To_v1 (line 333) | func Convert_VisitorConf_To_v1(conf VisitorConf) v1.VisitorConfigurer {

FILE: pkg/config/legacy/parse.go
  function ParseClientConfig (line 24) | func ParseClientConfig(filePath string) (
  function getIncludeContents (line 68) | func getIncludeContents(paths []string) ([]byte, error) {

FILE: pkg/config/legacy/proxy.go
  type ProxyType (line 26) | type ProxyType
  constant ProxyTypeTCP (line 29) | ProxyTypeTCP    ProxyType = "tcp"
  constant ProxyTypeUDP (line 30) | ProxyTypeUDP    ProxyType = "udp"
  constant ProxyTypeTCPMUX (line 31) | ProxyTypeTCPMUX ProxyType = "tcpmux"
  constant ProxyTypeHTTP (line 32) | ProxyTypeHTTP   ProxyType = "http"
  constant ProxyTypeHTTPS (line 33) | ProxyTypeHTTPS  ProxyType = "https"
  constant ProxyTypeSTCP (line 34) | ProxyTypeSTCP   ProxyType = "stcp"
  constant ProxyTypeXTCP (line 35) | ProxyTypeXTCP   ProxyType = "xtcp"
  constant ProxyTypeSUDP (line 36) | ProxyTypeSUDP   ProxyType = "sudp"
  type ProxyConf (line 53) | type ProxyConf interface
  function NewConfByType (line 61) | func NewConfByType(proxyType ProxyType) ProxyConf {
  function DefaultProxyConf (line 73) | func DefaultProxyConf(proxyType ProxyType) ProxyConf {
  function NewProxyConfFromIni (line 78) | func NewProxyConfFromIni(prefix, name string, section *ini.Section) (Pro...
  type LocalSvrConf (line 98) | type LocalSvrConf struct
  type HealthCheckConf (line 115) | type HealthCheckConf struct
  type BaseProxyConf (line 146) | type BaseProxyConf struct
    method GetBaseConfig (line 190) | func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
    method decorate (line 195) | func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini...
  type DomainConf (line 213) | type DomainConf struct
  type RoleServerCommonConf (line 218) | type RoleServerCommonConf struct
  type HTTPProxyConf (line 225) | type HTTPProxyConf struct
    method UnmarshalFromIni (line 237) | func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string,...
  type HTTPSProxyConf (line 249) | type HTTPSProxyConf struct
    method UnmarshalFromIni (line 254) | func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string...
  type TCPProxyConf (line 265) | type TCPProxyConf struct
    method UnmarshalFromIni (line 270) | func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, ...
  type UDPProxyConf (line 282) | type UDPProxyConf struct
    method UnmarshalFromIni (line 288) | func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, ...
  type TCPMuxProxyConf (line 300) | type TCPMuxProxyConf struct
    method UnmarshalFromIni (line 310) | func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name strin...
  type STCPProxyConf (line 322) | type STCPProxyConf struct
    method UnmarshalFromIni (line 327) | func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string,...
  type XTCPProxyConf (line 341) | type XTCPProxyConf struct
    method UnmarshalFromIni (line 346) | func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string,...
  type SUDPProxyConf (line 360) | type SUDPProxyConf struct
    method UnmarshalFromIni (line 365) | func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string,...
  function preUnmarshalFromIni (line 375) | func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, sect...

FILE: pkg/config/legacy/server.go
  type HTTPPluginOptions (line 25) | type HTTPPluginOptions struct
  type ServerCommonConf (line 36) | type ServerCommonConf struct
  function GetDefaultServerConf (line 207) | func GetDefaultServerConf() ServerCommonConf {
  function UnmarshalServerConfFromIni (line 220) | func UnmarshalServerConfFromIni(source any) (ServerCommonConf, error) {
  function loadHTTPPluginOpt (line 269) | func loadHTTPPluginOpt(section *ini.Section) (*HTTPPluginOptions, error) {

FILE: pkg/config/legacy/utils.go
  function GetMapWithoutPrefix (line 21) | func GetMapWithoutPrefix(set map[string]string, prefix string) map[strin...
  function GetMapByPrefix (line 37) | func GetMapByPrefix(set map[string]string, prefix string) map[string]str...

FILE: pkg/config/legacy/value.go
  function init (line 26) | func init() {
  type Values (line 38) | type Values struct
  function GetValues (line 42) | func GetValues() *Values {
  function RenderContent (line 48) | func RenderContent(in []byte) (out []byte, err error) {
  function GetRenderedConfFromFile (line 65) | func GetRenderedConfFromFile(path string) (out []byte, err error) {

FILE: pkg/config/legacy/visitor.go
  type VisitorType (line 24) | type VisitorType
  constant VisitorTypeSTCP (line 27) | VisitorTypeSTCP VisitorType = "stcp"
  constant VisitorTypeXTCP (line 28) | VisitorTypeXTCP VisitorType = "xtcp"
  constant VisitorTypeSUDP (line 29) | VisitorTypeSUDP VisitorType = "sudp"
  type VisitorConf (line 41) | type VisitorConf interface
  function DefaultVisitorConf (line 50) | func DefaultVisitorConf(visitorType VisitorType) VisitorConf {
  type BaseVisitorConf (line 58) | type BaseVisitorConf struct
    method GetBaseConfig (line 76) | func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
    method unmarshalFromIni (line 80) | func (cfg *BaseVisitorConf) unmarshalFromIni(_ string, name string, _ ...
  function preVisitorUnmarshalFromIni (line 91) | func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name str...
  type SUDPVisitorConf (line 104) | type SUDPVisitorConf struct
    method UnmarshalFromIni (line 108) | func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name strin...
  type STCPVisitorConf (line 119) | type STCPVisitorConf struct
    method UnmarshalFromIni (line 123) | func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name strin...
  type XTCPVisitorConf (line 134) | type XTCPVisitorConf struct
    method UnmarshalFromIni (line 145) | func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name strin...
  function NewVisitorConfFromIni (line 168) | func NewVisitorConfFromIni(prefix string, name string, section *ini.Sect...

FILE: pkg/config/load.go
  function init (line 43) | func init() {
  type Values (line 55) | type Values struct
  function GetValues (line 59) | func GetValues() *Values {
  function DetectLegacyINIFormat (line 65) | func DetectLegacyINIFormat(content []byte) bool {
  function DetectLegacyINIFormatFromFile (line 76) | func DetectLegacyINIFormatFromFile(path string) bool {
  function RenderWithTemplate (line 84) | func RenderWithTemplate(in []byte, values *Values) ([]byte, error) {
  function LoadFileContentWithTemplate (line 100) | func LoadFileContentWithTemplate(path string, values *Values) ([]byte, e...
  function LoadConfigureFromFile (line 108) | func LoadConfigureFromFile(path string, c any, strict bool) error {
  function detectFormatFromPath (line 117) | func detectFormatFromPath(path string) string {
  function parseYAMLWithDotFieldsHandling (line 132) | func parseYAMLWithDotFieldsHandling(content []byte, target any) error {
  function decodeJSONContent (line 155) | func decodeJSONContent(content []byte, target any, strict bool) error {
  function LoadConfigure (line 176) | func LoadConfigure(b []byte, c any, strict bool, formats ...string) error {
  function formatTOMLError (line 220) | func formatTOMLError(err error) error {
  function enhanceDecodeError (line 234) | func enhanceDecodeError(err error, originalContent []byte, includeLine b...
  function findFieldLineInContent (line 250) | func findFieldLineInContent(content []byte, fieldPath string) int {
  function NewProxyConfigurerFromMsg (line 278) | func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConf...
  function LoadServerConfig (line 295) | func LoadServerConfig(path string, strict bool) (*v1.ServerConfig, bool,...
  type ClientConfigLoadResult (line 327) | type ClientConfigLoadResult struct
  function LoadClientConfigResult (line 346) | func LoadClientConfigResult(path string, strict bool) (*ClientConfigLoad...
  function LoadClientConfig (line 400) | func LoadClientConfig(path string, strict bool) (
  function CompleteProxyConfigurers (line 420) | func CompleteProxyConfigurers(proxies []v1.ProxyConfigurer) []v1.ProxyCo...
  function CompleteVisitorConfigurers (line 428) | func CompleteVisitorConfigurers(visitors []v1.VisitorConfigurer) []v1.Vi...
  function FilterClientConfigurers (line 436) | func FilterClientConfigurers(
  function LoadAdditionalClientConfigs (line 473) | func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool, st...

FILE: pkg/config/load_test.go
  constant tomlServerContent (line 28) | tomlServerContent = `
  constant yamlServerContent (line 37) | yamlServerContent = `
  constant jsonServerContent (line 47) | jsonServerContent = `
  function TestLoadServerConfig (line 60) | func TestLoadServerConfig(t *testing.T) {
  function TestLoadServerConfigStrictMode (line 86) | func TestLoadServerConfigStrictMode(t *testing.T) {
  function TestRenderWithTemplate (line 116) | func TestRenderWithTemplate(t *testing.T) {
  function TestCustomStructStrictMode (line 139) | func TestCustomStructStrictMode(t *testing.T) {
  function TestLoadClientConfigStrictMode_UnknownPluginField (line 192) | func TestLoadClientConfigStrictMode_UnknownPluginField(t *testing.T) {
  function TestYAMLMergeInStrictMode (line 219) | func TestYAMLMergeInStrictMode(t *testing.T) {
  function TestOptimizedYAMLProcessing (line 265) | func TestOptimizedYAMLProcessing(t *testing.T) {
  function TestFilterClientConfigurers_PreserveRawNamesAndNoMutation (line 302) | func TestFilterClientConfigurers_PreserveRawNamesAndNoMutation(t *testin...
  function TestCompleteProxyConfigurers_PreserveRawNames (line 343) | func TestCompleteProxyConfigurers_PreserveRawNames(t *testing.T) {
  function TestCompleteVisitorConfigurers_PreserveRawNames (line 361) | func TestCompleteVisitorConfigurers_PreserveRawNames(t *testing.T) {
  function TestCompleteProxyConfigurers_Idempotent (line 387) | func TestCompleteProxyConfigurers_Idempotent(t *testing.T) {
  function TestCompleteVisitorConfigurers_Idempotent (line 406) | func TestCompleteVisitorConfigurers_Idempotent(t *testing.T) {
  function TestFilterClientConfigurers_FilterByStartAndEnabled (line 427) | func TestFilterClientConfigurers_FilterByStartAndEnabled(t *testing.T) {
  function TestYAMLEdgeCases (line 466) | func TestYAMLEdgeCases(t *testing.T) {
  function TestTOMLSyntaxErrorWithPosition (line 499) | func TestTOMLSyntaxErrorWithPosition(t *testing.T) {
  function TestTOMLTypeMismatchErrorWithFieldInfo (line 518) | func TestTOMLTypeMismatchErrorWithFieldInfo(t *testing.T) {
  function TestFindFieldLineInContent (line 536) | func TestFindFieldLineInContent(t *testing.T) {
  function TestFormatDetection (line 566) | func TestFormatDetection(t *testing.T) {
  function TestValidTOMLStillWorks (line 587) | func TestValidTOMLStillWorks(t *testing.T) {

FILE: pkg/config/source/aggregator.go
  type Aggregator (line 28) | type Aggregator struct
    method SetStoreSource (line 44) | func (a *Aggregator) SetStoreSource(storeSource *StoreSource) {
    method ConfigSource (line 51) | func (a *Aggregator) ConfigSource() *ConfigSource {
    method StoreSource (line 55) | func (a *Aggregator) StoreSource() *StoreSource {
    method getSourcesLocked (line 59) | func (a *Aggregator) getSourcesLocked() []Source {
    method Load (line 70) | func (a *Aggregator) Load() ([]v1.ProxyConfigurer, []v1.VisitorConfigu...
    method mapsToSortedSlices (line 98) | func (a *Aggregator) mapsToSortedSlices(
  function NewAggregator (line 35) | func NewAggregator(configSource *ConfigSource) *Aggregator {

FILE: pkg/config/source/aggregator_test.go
  function mockProxy (line 27) | func mockProxy(name string) v1.ProxyConfigurer {
  function mockVisitor (line 37) | func mockVisitor(name string) v1.VisitorConfigurer {
  function newTestStoreSource (line 45) | func newTestStoreSource(t *testing.T) *StoreSource {
  function newTestAggregator (line 54) | func newTestAggregator(t *testing.T, storeSource *StoreSource) *Aggregat...
  function TestNewAggregator_CreatesConfigSourceWhenNil (line 65) | func TestNewAggregator_CreatesConfigSourceWhenNil(t *testing.T) {
  function TestNewAggregator_WithoutStore (line 74) | func TestNewAggregator_WithoutStore(t *testing.T) {
  function TestNewAggregator_WithStore (line 84) | func TestNewAggregator_WithStore(t *testing.T) {
  function TestAggregator_SetStoreSource_Overwrite (line 96) | func TestAggregator_SetStoreSource_Overwrite(t *testing.T) {
  function TestAggregator_MergeBySourceOrder (line 113) | func TestAggregator_MergeBySourceOrder(t *testing.T) {
  function TestAggregator_DisabledEntryIsSourceLocalFilter (line 154) | func TestAggregator_DisabledEntryIsSourceLocalFilter(t *testing.T) {
  function TestAggregator_VisitorMerge (line 183) | func TestAggregator_VisitorMerge(t *testing.T) {
  function TestAggregator_Load_ReturnsSortedByName (line 199) | func TestAggregator_Load_ReturnsSortedByName(t *testing.T) {
  function TestAggregator_Load_ReturnsDefensiveCopies (line 220) | func TestAggregator_Load_ReturnsDefensiveCopies(t *testing.T) {

FILE: pkg/config/source/base_source.go
  type baseSource (line 26) | type baseSource struct
    method Load (line 42) | func (s *baseSource) Load() ([]v1.ProxyConfigurer, []v1.VisitorConfigu...
  function newBaseSource (line 33) | func newBaseSource() baseSource {

FILE: pkg/config/source/base_source_test.go
  function TestBaseSourceLoadReturnsClonedConfigurers (line 11) | func TestBaseSourceLoadReturnsClonedConfigurers(t *testing.T) {

FILE: pkg/config/source/clone.go
  function cloneConfigurers (line 23) | func cloneConfigurers(

FILE: pkg/config/source/config_source.go
  type ConfigSource (line 25) | type ConfigSource struct
    method ReplaceAll (line 36) | func (s *ConfigSource) ReplaceAll(proxies []v1.ProxyConfigurer, visito...
  function NewConfigSource (line 29) | func NewConfigSource() *ConfigSource {

FILE: pkg/config/source/config_source_test.go
  function TestNewConfigSource (line 25) | func TestNewConfigSource(t *testing.T) {
  function TestConfigSource_ReplaceAll (line 32) | func TestConfigSource_ReplaceAll(t *testing.T) {
  function TestConfigSource_Load (line 70) | func TestConfigSource_Load(t *testing.T) {
  function TestConfigSource_Load_FiltersDisabled (line 89) | func TestConfigSource_Load_FiltersDisabled(t *testing.T) {
  function TestConfigSource_ReplaceAll_DoesNotApplyRuntimeDefaults (line 146) | func TestConfigSource_ReplaceAll_DoesNotApplyRuntimeDefaults(t *testing....

FILE: pkg/config/source/source.go
  type Source (line 25) | type Source interface

FILE: pkg/config/source/store.go
  type StoreSourceConfig (line 27) | type StoreSourceConfig struct
  type storeData (line 31) | type storeData struct
  type StoreSource (line 36) | type StoreSource struct
    method loadFromFile (line 65) | func (s *StoreSource) loadFromFile() error {
    method loadFromFileUnlocked (line 71) | func (s *StoreSource) loadFromFileUnlocked() error {
    method saveToFileUnlocked (line 120) | func (s *StoreSource) saveToFileUnlocked() error {
    method AddProxy (line 175) | func (s *StoreSource) AddProxy(proxy v1.ProxyConfigurer) error {
    method UpdateProxy (line 201) | func (s *StoreSource) UpdateProxy(proxy v1.ProxyConfigurer) error {
    method RemoveProxy (line 228) | func (s *StoreSource) RemoveProxy(name string) error {
    method GetProxy (line 250) | func (s *StoreSource) GetProxy(name string) v1.ProxyConfigurer {
    method AddVisitor (line 261) | func (s *StoreSource) AddVisitor(visitor v1.VisitorConfigurer) error {
    method UpdateVisitor (line 287) | func (s *StoreSource) UpdateVisitor(visitor v1.VisitorConfigurer) error {
    method RemoveVisitor (line 314) | func (s *StoreSource) RemoveVisitor(name string) error {
    method GetVisitor (line 336) | func (s *StoreSource) GetVisitor(name string) v1.VisitorConfigurer {
    method GetAllProxies (line 347) | func (s *StoreSource) GetAllProxies() ([]v1.ProxyConfigurer, error) {
    method GetAllVisitors (line 358) | func (s *StoreSource) GetAllVisitors() ([]v1.VisitorConfigurer, error) {
  function NewStoreSource (line 46) | func NewStoreSource(cfg StoreSourceConfig) (*StoreSource, error) {

FILE: pkg/config/source/store_test.go
  function TestStoreSource_AddProxyAndVisitor_DoesNotApplyRuntimeDefaults (line 28) | func TestStoreSource_AddProxyAndVisitor_DoesNotApplyRuntimeDefaults(t *t...
  function TestStoreSource_LoadFromFile_DoesNotApplyRuntimeDefaults (line 62) | func TestStoreSource_LoadFromFile_DoesNotApplyRuntimeDefaults(t *testing...
  function TestStoreSource_LoadFromFile_UnknownFieldsAreIgnored (line 101) | func TestStoreSource_LoadFromFile_UnknownFieldsAreIgnored(t *testing.T) {

FILE: pkg/config/template.go
  type NumberPair (line 23) | type NumberPair struct
  function parseNumberRangePair (line 28) | func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]Numbe...
  function parseNumberRange (line 50) | func parseNumberRange(firstRangeStr string) ([]int64, error) {

FILE: pkg/config/types/types.go
  constant MB (line 26) | MB = 1024 * 1024
  constant KB (line 27) | KB = 1024
  constant BandwidthLimitModeClient (line 29) | BandwidthLimitModeClient = "client"
  constant BandwidthLimitModeServer (line 30) | BandwidthLimitModeServer = "server"
  type BandwidthQuantity (line 33) | type BandwidthQuantity struct
    method Equal (line 48) | func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool {
    method String (line 58) | func (q *BandwidthQuantity) String() string {
    method UnmarshalString (line 62) | func (q *BandwidthQuantity) UnmarshalString(s string) error {
    method UnmarshalJSON (line 91) | func (q *BandwidthQuantity) UnmarshalJSON(b []byte) error {
    method MarshalJSON (line 105) | func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
    method Bytes (line 109) | func (q *BandwidthQuantity) Bytes() int64 {
  function NewBandwidthQuantity (line 39) | func NewBandwidthQuantity(s string) (BandwidthQuantity, error) {
  type PortsRange (line 113) | type PortsRange struct
  type PortsRangeSlice (line 119) | type PortsRangeSlice
    method String (line 121) | func (p PortsRangeSlice) String() string {
  function NewPortsRangeSliceFromString (line 137) | func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {

FILE: pkg/config/types/types_test.go
  type Wrap (line 24) | type Wrap struct
  function TestBandwidthQuantity (line 29) | func TestBandwidthQuantity(t *testing.T) {
  function TestBandwidthQuantity_MB (line 42) | func TestBandwidthQuantity_MB(t *testing.T) {
  function TestBandwidthQuantity_InvalidUnit (line 55) | func TestBandwidthQuantity_InvalidUnit(t *testing.T) {
  function TestBandwidthQuantity_InvalidNumber (line 61) | func TestBandwidthQuantity_InvalidNumber(t *testing.T) {
  function TestPortsRangeSlice2String (line 67) | func TestPortsRangeSlice2String(t *testing.T) {
  function TestNewPortsRangeSliceFromString (line 83) | func TestNewPortsRangeSliceFromString(t *testing.T) {

FILE: pkg/config/v1/api.go
  type APIMetadata (line 17) | type APIMetadata struct

FILE: pkg/config/v1/client.go
  type ClientConfig (line 25) | type ClientConfig struct
  type ClientCommonConfig (line 32) | type ClientCommonConfig struct
    method Complete (line 85) | func (c *ClientCommonConfig) Complete() error {
  type ClientTransportConfig (line 102) | type ClientTransportConfig struct
    method Complete (line 144) | func (c *ClientTransportConfig) Complete() {
  type TLSClientConfig (line 164) | type TLSClientConfig struct
    method Complete (line 178) | func (c *TLSClientConfig) Complete() {
  type AuthClientConfig (line 183) | type AuthClientConfig struct
    method Complete (line 202) | func (c *AuthClientConfig) Complete() error {
  type AuthOIDCClientConfig (line 207) | type AuthOIDCClientConfig struct
  type VirtualNetConfig (line 240) | type VirtualNetConfig struct

FILE: pkg/config/v1/client_test.go
  function TestClientConfigComplete (line 24) | func TestClientConfigComplete(t *testing.T) {
  function TestAuthClientConfig_Complete (line 38) | func TestAuthClientConfig_Complete(t *testing.T) {

FILE: pkg/config/v1/clone_test.go
  function TestProxyCloneDeepCopy (line 9) | func TestProxyCloneDeepCopy(t *testing.T) {
  function TestVisitorCloneDeepCopy (line 75) | func TestVisitorCloneDeepCopy(t *testing.T) {

FILE: pkg/config/v1/common.go
  type AuthScope (line 23) | type AuthScope
  constant AuthScopeHeartBeats (line 26) | AuthScopeHeartBeats   AuthScope = "HeartBeats"
  constant AuthScopeNewWorkConns (line 27) | AuthScopeNewWorkConns AuthScope = "NewWorkConns"
  type AuthMethod (line 30) | type AuthMethod
  constant AuthMethodToken (line 33) | AuthMethodToken AuthMethod = "token"
  constant AuthMethodOIDC (line 34) | AuthMethodOIDC  AuthMethod = "oidc"
  type QUICOptions (line 38) | type QUICOptions struct
    method Complete (line 44) | func (c *QUICOptions) Complete() {
  type WebServerConfig (line 50) | type WebServerConfig struct
    method Complete (line 71) | func (c *WebServerConfig) Complete() {
  type TLSConfig (line 75) | type TLSConfig struct
  type NatTraversalConfig (line 88) | type NatTraversalConfig struct
    method Clone (line 95) | func (c *NatTraversalConfig) Clone() *NatTraversalConfig {
  type LogConfig (line 103) | type LogConfig struct
    method Complete (line 119) | func (c *LogConfig) Complete() {
  type HTTPPluginOptions (line 125) | type HTTPPluginOptions struct
  type HeaderOperations (line 133) | type HeaderOperations struct
    method Clone (line 137) | func (o HeaderOperations) Clone() HeaderOperations {
  type HTTPHeader (line 143) | type HTTPHeader struct

FILE: pkg/config/v1/decode.go
  type DecodeOptions (line 25) | type DecodeOptions struct
  function decodeJSONWithOptions (line 29) | func decodeJSONWithOptions(b []byte, out any, options DecodeOptions) err...
  function isJSONNull (line 35) | func isJSONNull(b []byte) bool {
  type typedEnvelope (line 39) | type typedEnvelope struct
  function DecodeProxyConfigurerJSON (line 44) | func DecodeProxyConfigurerJSON(b []byte, options DecodeOptions) (ProxyCo...
  function DecodeVisitorConfigurerJSON (line 72) | func DecodeVisitorConfigurerJSON(b []byte, options DecodeOptions) (Visit...
  function DecodeClientPluginOptionsJSON (line 100) | func DecodeClientPluginOptionsJSON(b []byte, options DecodeOptions) (Typ...
  function DecodeVisitorPluginOptionsJSON (line 127) | func DecodeVisitorPluginOptionsJSON(b []byte, options DecodeOptions) (Ty...
  function DecodeClientConfigJSON (line 154) | func DecodeClientConfigJSON(b []byte, options DecodeOptions) (ClientConf...

FILE: pkg/config/v1/decode_test.go
  function TestDecodeProxyConfigurerJSON_StrictPluginUnknownFields (line 23) | func TestDecodeProxyConfigurerJSON_StrictPluginUnknownFields(t *testing....
  function TestDecodeVisitorConfigurerJSON_StrictPluginUnknownFields (line 44) | func TestDecodeVisitorConfigurerJSON_StrictPluginUnknownFields(t *testin...
  function TestDecodeClientConfigJSON_StrictUnknownProxyField (line 66) | func TestDecodeClientConfigJSON_StrictUnknownProxyField(t *testing.T) {

FILE: pkg/config/v1/proxy.go
  type ProxyTransport (line 28) | type ProxyTransport struct
  type LoadBalancerConfig (line 49) | type LoadBalancerConfig struct
  type ProxyBackend (line 59) | type ProxyBackend struct
    method Clone (line 144) | func (c ProxyBackend) Clone() ProxyBackend {
  type HealthCheckConfig (line 72) | type HealthCheckConfig struct
    method Clone (line 102) | func (c HealthCheckConfig) Clone() HealthCheckConfig {
  type DomainConfig (line 108) | type DomainConfig struct
    method Clone (line 113) | func (c DomainConfig) Clone() DomainConfig {
  type ProxyBaseConfig (line 119) | type ProxyBaseConfig struct
    method Clone (line 134) | func (c ProxyBaseConfig) Clone() ProxyBaseConfig {
    method GetBaseConfig (line 150) | func (c *ProxyBaseConfig) GetBaseConfig() *ProxyBaseConfig {
    method Complete (line 154) | func (c *ProxyBaseConfig) Complete() {
    method MarshalToMsg (line 163) | func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 179) | func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
  type TypedProxyConfig (line 196) | type TypedProxyConfig struct
    method UnmarshalJSON (line 201) | func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
    method MarshalJSON (line 212) | func (c *TypedProxyConfig) MarshalJSON() ([]byte, error) {
  type ProxyConfigurer (line 216) | type ProxyConfigurer interface
  type ProxyType (line 228) | type ProxyType
  constant ProxyTypeTCP (line 231) | ProxyTypeTCP    ProxyType = "tcp"
  constant ProxyTypeUDP (line 232) | ProxyTypeUDP    ProxyType = "udp"
  constant ProxyTypeTCPMUX (line 233) | ProxyTypeTCPMUX ProxyType = "tcpmux"
  constant ProxyTypeHTTP (line 234) | ProxyTypeHTTP   ProxyType = "http"
  constant ProxyTypeHTTPS (line 235) | ProxyTypeHTTPS  ProxyType = "https"
  constant ProxyTypeSTCP (line 236) | ProxyTypeSTCP   ProxyType = "stcp"
  constant ProxyTypeXTCP (line 237) | ProxyTypeXTCP   ProxyType = "xtcp"
  constant ProxyTypeSUDP (line 238) | ProxyTypeSUDP   ProxyType = "sudp"
  function NewProxyConfigurerByType (line 252) | func NewProxyConfigurerByType(proxyType ProxyType) ProxyConfigurer {
  type TCPProxyConfig (line 264) | type TCPProxyConfig struct
    method MarshalToMsg (line 270) | func (c *TCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 276) | func (c *TCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 282) | func (c *TCPProxyConfig) Clone() ProxyConfigurer {
  type UDPProxyConfig (line 290) | type UDPProxyConfig struct
    method MarshalToMsg (line 296) | func (c *UDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 302) | func (c *UDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 308) | func (c *UDPProxyConfig) Clone() ProxyConfigurer {
  type HTTPProxyConfig (line 316) | type HTTPProxyConfig struct
    method MarshalToMsg (line 329) | func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 343) | func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 357) | func (c *HTTPProxyConfig) Clone() ProxyConfigurer {
  type HTTPSProxyConfig (line 369) | type HTTPSProxyConfig struct
    method MarshalToMsg (line 374) | func (c *HTTPSProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 381) | func (c *HTTPSProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 388) | func (c *HTTPSProxyConfig) Clone() ProxyConfigurer {
  type TCPMultiplexerType (line 395) | type TCPMultiplexerType
  constant TCPMultiplexerHTTPConnect (line 398) | TCPMultiplexerHTTPConnect TCPMultiplexerType = "httpconnect"
  type TCPMuxProxyConfig (line 403) | type TCPMuxProxyConfig struct
    method MarshalToMsg (line 413) | func (c *TCPMuxProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 424) | func (c *TCPMuxProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 435) | func (c *TCPMuxProxyConfig) Clone() ProxyConfigurer {
  type STCPProxyConfig (line 444) | type STCPProxyConfig struct
    method MarshalToMsg (line 451) | func (c *STCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 458) | func (c *STCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 465) | func (c *STCPProxyConfig) Clone() ProxyConfigurer {
  type XTCPProxyConfig (line 474) | type XTCPProxyConfig struct
    method MarshalToMsg (line 484) | func (c *XTCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 491) | func (c *XTCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 498) | func (c *XTCPProxyConfig) Clone() ProxyConfigurer {
  type SUDPProxyConfig (line 508) | type SUDPProxyConfig struct
    method MarshalToMsg (line 515) | func (c *SUDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
    method UnmarshalFromMsg (line 522) | func (c *SUDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
    method Clone (line 529) | func (c *SUDPProxyConfig) Clone() ProxyConfigurer {

FILE: pkg/config/v1/proxy_plugin.go
  constant PluginHTTP2HTTPS (line 27) | PluginHTTP2HTTPS       = "http2https"
  constant PluginHTTPProxy (line 28) | PluginHTTPProxy        = "http_proxy"
  constant PluginHTTPS2HTTP (line 29) | PluginHTTPS2HTTP       = "https2http"
  constant PluginHTTPS2HTTPS (line 30) | PluginHTTPS2HTTPS      = "https2https"
  constant PluginHTTP2HTTP (line 31) | PluginHTTP2HTTP        = "http2http"
  constant PluginSocks5 (line 32) | PluginSocks5           = "socks5"
  constant PluginStaticFile (line 33) | PluginStaticFile       = "static_file"
  constant PluginUnixDomainSocket (line 34) | PluginUnixDomainSocket = "unix_domain_socket"
  constant PluginTLS2Raw (line 35) | PluginTLS2Raw          = "tls2raw"
  constant PluginVirtualNet (line 36) | PluginVirtualNet       = "virtual_net"
  type ClientPluginOptions (line 52) | type ClientPluginOptions interface
  type TypedClientPluginOptions (line 57) | type TypedClientPluginOptions struct
    method Clone (line 62) | func (c TypedClientPluginOptions) Clone() TypedClientPluginOptions {
    method UnmarshalJSON (line 70) | func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
    method MarshalJSON (line 79) | func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
  type HTTP2HTTPSPluginOptions (line 83) | type HTTP2HTTPSPluginOptions struct
    method Complete (line 90) | func (o *HTTP2HTTPSPluginOptions) Complete() {}
    method Clone (line 92) | func (o *HTTP2HTTPSPluginOptions) Clone() ClientPluginOptions {
  type HTTPProxyPluginOptions (line 101) | type HTTPProxyPluginOptions struct
    method Complete (line 107) | func (o *HTTPProxyPluginOptions) Complete() {}
    method Clone (line 109) | func (o *HTTPProxyPluginOptions) Clone() ClientPluginOptions {
  type HTTPS2HTTPPluginOptions (line 117) | type HTTPS2HTTPPluginOptions struct
    method Complete (line 127) | func (o *HTTPS2HTTPPluginOptions) Complete() {
    method Clone (line 131) | func (o *HTTPS2HTTPPluginOptions) Clone() ClientPluginOptions {
  type HTTPS2HTTPSPluginOptions (line 141) | type HTTPS2HTTPSPluginOptions struct
    method Complete (line 151) | func (o *HTTPS2HTTPSPluginOptions) Complete() {
    method Clone (line 155) | func (o *HTTPS2HTTPSPluginOptions) Clone() ClientPluginOptions {
  type HTTP2HTTPPluginOptions (line 165) | type HTTP2HTTPPluginOptions struct
    method Complete (line 172) | func (o *HTTP2HTTPPluginOptions) Complete() {}
    method Clone (line 174) | func (o *HTTP2HTTPPluginOptions) Clone() ClientPluginOptions {
  type Socks5PluginOptions (line 183) | type Socks5PluginOptions struct
    method Complete (line 189) | func (o *Socks5PluginOptions) Complete() {}
    method Clone (line 191) | func (o *Socks5PluginOptions) Clone() ClientPluginOptions {
  type StaticFilePluginOptions (line 199) | type StaticFilePluginOptions struct
    method Complete (line 207) | func (o *StaticFilePluginOptions) Complete() {}
    method Clone (line 209) | func (o *StaticFilePluginOptions) Clone() ClientPluginOptions {
  type UnixDomainSocketPluginOptions (line 217) | type UnixDomainSocketPluginOptions struct
    method Complete (line 222) | func (o *UnixDomainSocketPluginOptions) Complete() {}
    method Clone (line 224) | func (o *UnixDomainSocketPluginOptions) Clone() ClientPluginOptions {
  type TLS2RawPluginOptions (line 232) | type TLS2RawPluginOptions struct
    method Complete (line 239) | func (o *TLS2RawPluginOptions) Complete() {}
    method Clone (line 241) | func (o *TLS2RawPluginOptions) Clone() ClientPluginOptions {
  type VirtualNetPluginOptions (line 249) | type VirtualNetPluginOptions struct
    method Complete (line 253) | func (o *VirtualNetPluginOptions) Complete() {}
    method Clone (line 255) | func (o *VirtualNetPluginOptions) Clone() ClientPluginOptions {

FILE: pkg/config/v1/proxy_test.go
  function TestUnmarshalTypedProxyConfig (line 24) | func TestUnmarshalTypedProxyConfig(t *testing.T) {

FILE: pkg/config/v1/server.go
  type ServerConfig (line 24) | type ServerConfig struct
    method Complete (line 101) | func (c *ServerConfig) Complete() error {
  type AuthServerConfig (line 128) | type AuthServerConfig struct
    method Complete (line 136) | func (c *AuthServerConfig) Complete() error {
  type AuthOIDCServerConfig (line 141) | type AuthOIDCServerConfig struct
  type ServerTransportConfig (line 157) | type ServerTransportConfig struct
    method Complete (line 182) | func (c *ServerTransportConfig) Complete() {
  type TLSServerConfig (line 199) | type TLSServerConfig struct
  type SSHTunnelGateway (line 206) | type SSHTunnelGateway struct
    method Complete (line 213) | func (c *SSHTunnelGateway) Complete() {

FILE: pkg/config/v1/server_test.go
  function TestServerConfigComplete (line 24) | func TestServerConfigComplete(t *testing.T) {
  function TestAuthServerConfig_Complete (line 35) | func TestAuthServerConfig_Complete(t *testing.T) {

FILE: pkg/config/v1/store.go
  type StoreConfig (line 18) | type StoreConfig struct
    method IsEnabled (line 24) | func (c *StoreConfig) IsEnabled() bool {

FILE: pkg/config/v1/validation/client.go
  method ValidateClientCommonConfig (line 30) | func (v *ConfigValidator) ValidateClientCommonConfig(c *v1.ClientCommonC...
  function validateFeatureGates (line 53) | func validateFeatureGates(c *v1.ClientCommonConfig) (Warning, error) {
  method validateAuthConfig (line 62) | func (v *ConfigValidator) validateAuthConfig(c *v1.AuthClientConfig) (Wa...
  method validateOIDCConfig (line 99) | func (v *ConfigValidator) validateOIDCConfig(c *v1.AuthOIDCClientConfig)...
  function validateTransportConfig (line 121) | func validateTransportConfig(c *v1.ClientTransportConfig) (Warning, erro...
  function validateIncludeFiles (line 152) | func validateIncludeFiles(files []string) (Warning, error) {
  function ValidateAllClientConfig (line 167) | func ValidateAllClientConfig(

FILE: pkg/config/v1/validation/common.go
  function validateWebServerConfig (line 24) | func validateWebServerConfig(c *v1.WebServerConfig) error {
  function ValidatePort (line 38) | func ValidatePort(port int, fieldPath string) error {
  function validateLogConfig (line 45) | func validateLogConfig(c *v1.LogConfig) error {

FILE: pkg/config/v1/validation/oidc.go
  function ValidateOIDCClientCredentialsConfig (line 25) | func ValidateOIDCClientCredentialsConfig(c *v1.AuthOIDCClientConfig) err...

FILE: pkg/config/v1/validation/oidc_test.go
  function TestValidateOIDCClientCredentialsConfig (line 27) | func TestValidateOIDCClientCredentialsConfig(t *testing.T) {

FILE: pkg/config/v1/validation/plugin.go
  function ValidateClientPluginOptions (line 23) | func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
  function validateHTTP2HTTPSPluginOptions (line 41) | func validateHTTP2HTTPSPluginOptions(c *v1.HTTP2HTTPSPluginOptions) error {
  function validateHTTPS2HTTPPluginOptions (line 48) | func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error {
  function validateHTTPS2HTTPSPluginOptions (line 55) | func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) er...
  function validateStaticFilePluginOptions (line 62) | func validateStaticFilePluginOptions(c *v1.StaticFilePluginOptions) error {
  function validateUnixDomainSocketPluginOptions (line 69) | func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginO...
  function validateTLS2RawPluginOptions (line 76) | func validateTLS2RawPluginOptions(c *v1.TLS2RawPluginOptions) error {

FILE: pkg/config/v1/validation/proxy.go
  function validateProxyBaseConfigForClient (line 28) | func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
  function validateProxyBaseConfigForServer (line 67) | func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig) error {
  function validateDomainConfigForClient (line 74) | func validateDomainConfigForClient(c *v1.DomainConfig) error {
  function validateDomainConfigForServer (line 81) | func validateDomainConfigForServer(c *v1.DomainConfig, s *v1.ServerConfi...
  function ValidateProxyConfigurerForClient (line 102) | func ValidateProxyConfigurerForClient(c v1.ProxyConfigurer) error {
  function validateTCPProxyConfigForClient (line 129) | func validateTCPProxyConfigForClient(c *v1.TCPProxyConfig) error {
  function validateUDPProxyConfigForClient (line 133) | func validateUDPProxyConfigForClient(c *v1.UDPProxyConfig) error {
  function validateTCPMuxProxyConfigForClient (line 137) | func validateTCPMuxProxyConfigForClient(c *v1.TCPMuxProxyConfig) error {
  function validateHTTPProxyConfigForClient (line 148) | func validateHTTPProxyConfigForClient(c *v1.HTTPProxyConfig) error {
  function validateHTTPSProxyConfigForClient (line 152) | func validateHTTPSProxyConfigForClient(c *v1.HTTPSProxyConfig) error {
  function validateSTCPProxyConfigForClient (line 156) | func validateSTCPProxyConfigForClient(c *v1.STCPProxyConfig) error {
  function validateXTCPProxyConfigForClient (line 160) | func validateXTCPProxyConfigForClient(c *v1.XTCPProxyConfig) error {
  function validateSUDPProxyConfigForClient (line 164) | func validateSUDPProxyConfigForClient(c *v1.SUDPProxyConfig) error {
  function ValidateProxyConfigurerForServer (line 168) | func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.Server...
  function validateTCPProxyConfigForServer (line 196) | func validateTCPProxyConfigForServer(c *v1.TCPProxyConfig, s *v1.ServerC...
  function validateUDPProxyConfigForServer (line 200) | func validateUDPProxyConfigForServer(c *v1.UDPProxyConfig, s *v1.ServerC...
  function validateTCPMuxProxyConfigForServer (line 204) | func validateTCPMuxProxyConfigForServer(c *v1.TCPMuxProxyConfig, s *v1.S...
  function validateHTTPProxyConfigForServer (line 213) | func validateHTTPProxyConfigForServer(c *v1.HTTPProxyConfig, s *v1.Serve...
  function validateHTTPSProxyConfigForServer (line 221) | func validateHTTPSProxyConfigForServer(c *v1.HTTPSProxyConfig, s *v1.Ser...
  function validateSTCPProxyConfigForServer (line 229) | func validateSTCPProxyConfigForServer(c *v1.STCPProxyConfig, s *v1.Serve...
  function validateXTCPProxyConfigForServer (line 233) | func validateXTCPProxyConfigForServer(c *v1.XTCPProxyConfig, s *v1.Serve...
  function validateSUDPProxyConfigForServer (line 237) | func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.Serve...
  function ValidateAnnotations (line 242) | func ValidateAnnotations(annotations map[string]string) error {
  constant TotalAnnotationSizeLimitB (line 259) | TotalAnnotationSizeLimitB int = 256 * (1 << 10)
  function ValidateAnnotationsSize (line 261) | func ValidateAnnotationsSize(annotations map[string]string) error {

FILE: pkg/config/v1/validation/server.go
  method ValidateServerConfig (line 27) | func (v *ConfigValidator) ValidateServerConfig(c *v1.ServerConfig) (Warn...

FILE: pkg/config/v1/validation/validation.go
  type Warning (line 61) | type Warning
  function AppendError (line 63) | func AppendError(err error, errs ...error) error {

FILE: pkg/config/v1/validation/validator.go
  type ConfigValidator (line 10) | type ConfigValidator struct
    method ValidateUnsafeFeature (line 22) | func (v *ConfigValidator) ValidateUnsafeFeature(feature string) error {
  function NewConfigValidator (line 15) | func NewConfigValidator(unsafeFeatures *security.UnsafeFeatures) *Config...

FILE: pkg/config/v1/validation/visitor.go
  function ValidateVisitorConfigurer (line 25) | func ValidateVisitorConfigurer(c v1.VisitorConfigurer) error {
  function validateVisitorBaseConfig (line 42) | func validateVisitorBaseConfig(c *v1.VisitorBaseConfig) error {
  function validateXTCPVisitorConfig (line 57) | func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error {

FILE: pkg/config/v1/value_source.go
  type ValueSource (line 28) | type ValueSource struct
    method Validate (line 52) | func (v *ValueSource) Validate() error {
    method Resolve (line 74) | func (v *ValueSource) Resolve(ctx context.Context) (string, error) {
  type FileSource (line 35) | type FileSource struct
    method Validate (line 90) | func (f *FileSource) Validate() error {
    method Resolve (line 102) | func (f *FileSource) Resolve(_ context.Context) (string, error) {
  type ExecSource (line 40) | type ExecSource struct
    method Validate (line 117) | func (e *ExecSource) Validate() error {
    method Resolve (line 138) | func (e *ExecSource) Resolve(ctx context.Context) (string, error) {
  type ExecEnvVar (line 46) | type ExecEnvVar struct

FILE: pkg/config/v1/value_source_test.go
  function TestValueSource_Validate (line 24) | func TestValueSource_Validate(t *testing.T) {
  function TestFileSource_Validate (line 82) | func TestFileSource_Validate(t *testing.T) {
  function TestFileSource_Resolve (line 126) | func TestFileSource_Resolve(t *testing.T) {
  function TestValueSource_Resolve (line 184) | func TestValueSource_Resolve(t *testing.T) {

FILE: pkg/config/v1/visitor.go
  type VisitorTransport (line 24) | type VisitorTransport struct
  type VisitorBaseConfig (line 29) | type VisitorBaseConfig struct
    method Clone (line 50) | func (c VisitorBaseConfig) Clone() VisitorBaseConfig {
    method GetBaseConfig (line 57) | func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
    method Complete (line 61) | func (c *VisitorBaseConfig) Complete() {
  type VisitorConfigurer (line 67) | type VisitorConfigurer interface
  type VisitorType (line 73) | type VisitorType
  constant VisitorTypeSTCP (line 76) | VisitorTypeSTCP VisitorType = "stcp"
  constant VisitorTypeXTCP (line 77) | VisitorTypeXTCP VisitorType = "xtcp"
  constant VisitorTypeSUDP (line 78) | VisitorTypeSUDP VisitorType = "sudp"
  type TypedVisitorConfig (line 87) | type TypedVisitorConfig struct
    method UnmarshalJSON (line 92) | func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
    method MarshalJSON (line 103) | func (c *TypedVisitorConfig) MarshalJSON() ([]byte, error) {
  function NewVisitorConfigurerByType (line 107) | func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
  type STCPVisitorConfig (line 119) | type STCPVisitorConfig struct
    method Clone (line 123) | func (c *STCPVisitorConfig) Clone() VisitorConfigurer {
  type SUDPVisitorConfig (line 131) | type SUDPVisitorConfig struct
    method Clone (line 135) | func (c *SUDPVisitorConfig) Clone() VisitorConfigurer {
  type XTCPVisitorConfig (line 143) | type XTCPVisitorConfig struct
    method Complete (line 157) | func (c *XTCPVisitorConfig) Complete() {
    method Clone (line 166) | func (c *XTCPVisitorConfig) Clone() VisitorConfigurer {

FILE: pkg/config/v1/visitor_plugin.go
  constant VisitorPluginVirtualNet (line 24) | VisitorPluginVirtualNet = "virtual_net"
  type VisitorPluginOptions (line 31) | type VisitorPluginOptions interface
  type TypedVisitorPluginOptions (line 36) | type TypedVisitorPluginOptions struct
    method Clone (line 41) | func (c TypedVisitorPluginOptions) Clone() TypedVisitorPluginOptions {
    method UnmarshalJSON (line 49) | func (c *TypedVisitorPluginOptions) UnmarshalJSON(b []byte) error {
    method MarshalJSON (line 58) | func (c *TypedVisitorPluginOptions) MarshalJSON() ([]byte, error) {
  type VirtualNetVisitorPluginOptions (line 62) | type VirtualNetVisitorPluginOptions struct
    method Complete (line 67) | func (o *VirtualNetVisitorPluginOptions) Complete() {}
    method Clone (line 69) | func (o *VirtualNetVisitorPluginOptions) Clone() VisitorPluginOptions {

FILE: pkg/metrics/aggregate/server.go
  function EnableMem (line 24) | func EnableMem() {
  function EnablePrometheus (line 29) | func EnablePrometheus() {
  function init (line 35) | func init() {
  type serverMetrics (line 39) | type serverMetrics struct
    method Add (line 43) | func (m *serverMetrics) Add(sm metrics.ServerMetrics) {
    method NewClient (line 47) | func (m *serverMetrics) NewClient() {
    method CloseClient (line 53) | func (m *serverMetrics) CloseClient() {
    method NewProxy (line 59) | func (m *serverMetrics) NewProxy(name string, proxyType string, user s...
    method CloseProxy (line 65) | func (m *serverMetrics) CloseProxy(name string, proxyType string) {
    method OpenConnection (line 71) | func (m *serverMetrics) OpenConnection(name string, proxyType string) {
    method CloseConnection (line 77) | func (m *serverMetrics) CloseConnection(name string, proxyType string) {
    method AddTrafficIn (line 83) | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, tr...
    method AddTrafficOut (line 89) | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, t...

FILE: pkg/metrics/mem/server.go
  function init (line 33) | func init() {
  type serverMetrics (line 39) | type serverMetrics struct
    method run (line 59) | func (m *serverMetrics) run() {
    method clearUselessInfo (line 70) | func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration tim...
    method ClearOfflineProxies (line 89) | func (m *serverMetrics) ClearOfflineProxies() (int, int) {
    method NewClient (line 93) | func (m *serverMetrics) NewClient() {
    method CloseClient (line 97) | func (m *serverMetrics) CloseClient() {
    method NewProxy (line 101) | func (m *serverMetrics) NewProxy(name string, proxyType string, user s...
    method CloseProxy (line 127) | func (m *serverMetrics) CloseProxy(name string, proxyType string) {
    method OpenConnection (line 138) | func (m *serverMetrics) OpenConnection(name string, _ string) {
    method CloseConnection (line 149) | func (m *serverMetrics) CloseConnection(name string, _ string) {
    method AddTrafficIn (line 160) | func (m *serverMetrics) AddTrafficIn(name string, _ string, trafficByt...
    method AddTrafficOut (line 172) | func (m *serverMetrics) AddTrafficOut(name string, _ string, trafficBy...
    method GetServer (line 186) | func (m *serverMetrics) GetServer() *ServerStats {
    method GetProxiesByType (line 221) | func (m *serverMetrics) GetProxiesByType(proxyType string) []*ProxySta...
    method GetProxiesByTypeAndName (line 235) | func (m *serverMetrics) GetProxiesByTypeAndName(proxyType string, prox...
    method GetProxyByName (line 246) | func (m *serverMetrics) GetProxyByName(proxyName string) (res *ProxySt...
    method GetProxyTraffic (line 257) | func (m *serverMetrics) GetProxyTraffic(name string) (res *ProxyTraffi...
  function newServerMetrics (line 44) | func newServerMetrics() *serverMetrics {
  function toProxyStats (line 202) | func toProxyStats(name string, proxyStats *ProxyStatistics) *ProxyStats {

FILE: pkg/metrics/mem/types.go
  constant ReserveDays (line 24) | ReserveDays = 7
  type ServerStats (line 27) | type ServerStats struct
  type ProxyStats (line 35) | type ProxyStats struct
  type ProxyTrafficInfo (line 47) | type ProxyTrafficInfo struct
  type ProxyStatistics (line 53) | type ProxyStatistics struct
  type ServerStatistics (line 65) | type ServerStatistics struct
  type Collector (line 81) | type Collector interface

FILE: pkg/metrics/prometheus/server.go
  constant namespace (line 10) | namespace       = "frp"
  constant serverSubsystem (line 11) | serverSubsystem = "server"
  type serverMetrics (line 16) | type serverMetrics struct
    method NewClient (line 25) | func (m *serverMetrics) NewClient() {
    method CloseClient (line 29) | func (m *serverMetrics) CloseClient() {
    method NewProxy (line 33) | func (m *serverMetrics) NewProxy(name string, proxyType string, _ stri...
    method CloseProxy (line 38) | func (m *serverMetrics) CloseProxy(name string, proxyType string) {
    method OpenConnection (line 43) | func (m *serverMetrics) OpenConnection(name string, proxyType string) {
    method CloseConnection (line 47) | func (m *serverMetrics) CloseConnection(name string, proxyType string) {
    method AddTrafficIn (line 51) | func (m *serverMetrics) AddTrafficIn(name string, proxyType string, tr...
    method AddTrafficOut (line 55) | func (m *serverMetrics) AddTrafficOut(name string, proxyType string, t...
  function newServerMetrics (line 59) | func newServerMetrics() *serverMetrics {

FILE: pkg/msg/ctl.go
  function init (line 27) | func init() {
  function ReadMsg (line 34) | func ReadMsg(c io.Reader) (msg Message, err error) {
  function ReadMsgInto (line 38) | func ReadMsgInto(c io.Reader, msg Message) (err error) {
  function WriteMsg (line 42) | func WriteMsg(c io.Writer, msg any) (err error) {

FILE: pkg/msg/handler.go
  function AsyncHandler (line 22) | func AsyncHandler(f func(Message)) func(Message) {
  type Dispatcher (line 29) | type Dispatcher struct
    method Run (line 48) | func (d *Dispatcher) Run() {
    method sendLoop (line 53) | func (d *Dispatcher) sendLoop() {
    method readLoop (line 64) | func (d *Dispatcher) readLoop() {
    method Send (line 80) | func (d *Dispatcher) Send(m Message) error {
    method RegisterHandler (line 89) | func (d *Dispatcher) RegisterHandler(msg Message, handler func(Message...
    method RegisterDefaultHandler (line 93) | func (d *Dispatcher) RegisterDefaultHandler(handler func(Message)) {
    method Done (line 97) | func (d *Dispatcher) Done() chan struct{} {
  function NewDispatcher (line 38) | func NewDispatcher(rw io.ReadWriter) *Dispatcher {

FILE: pkg/msg/msg.go
  constant TypeLogin (line 23) | TypeLogin              = 'o'
  constant TypeLoginResp (line 24) | TypeLoginResp          = '1'
  constant TypeNewProxy (line 25) | TypeNewProxy           = 'p'
  constant TypeNewProxyResp (line 26) | TypeNewProxyResp       = '2'
  constant TypeCloseProxy (line 27) | TypeCloseProxy         = 'c'
  constant TypeNewWorkConn (line 28) | TypeNewWorkConn        = 'w'
  constant TypeReqWorkConn (line 29) | TypeReqWorkConn        = 'r'
  constant TypeStartWorkConn (line 30) | TypeStartWorkConn      = 's'
  constant TypeNewVisitorConn (line 31) | TypeNewVisitorConn     = 'v'
  constant TypeNewVisitorConnResp (line 32) | TypeNewVisitorConnResp = '3'
  constant TypePing (line 33) | TypePing               = 'h'
  constant TypePong (line 34) | TypePong               = '4'
  constant TypeUDPPacket (line 35) | TypeUDPPacket          = 'u'
  constant TypeNatHoleVisitor (line 36) | TypeNatHoleVisitor     = 'i'
  constant TypeNatHoleClient (line 37) | TypeNatHoleClient      = 'n'
  constant TypeNatHoleResp (line 38) | TypeNatHoleResp        = 'm'
  constant TypeNatHoleSid (line 39) | TypeNatHoleSid         = '5'
  constant TypeNatHoleReport (line 40) | TypeNatHoleReport      = '6'
  type ClientSpec (line 66) | type ClientSpec struct
  type Login (line 76) | type Login struct
  type LoginResp (line 95) | type LoginResp struct
  type NewProxy (line 102) | type NewProxy struct
  type NewProxyResp (line 136) | type NewProxyResp struct
  type CloseProxy (line 142) | type CloseProxy struct
  type NewWorkConn (line 146) | type NewWorkConn struct
  type ReqWorkConn (line 152) | type ReqWorkConn struct
  type StartWorkConn (line 154) | type StartWorkConn struct
  type NewVisitorConn (line 163) | type NewVisitorConn struct
  type NewVisitorConnResp (line 172) | type NewVisitorConnResp struct
  type Ping (line 177) | type Ping struct
  type Pong (line 182) | type Pong struct
  type UDPPacket (line 186) | type UDPPacket struct
  type NatHoleVisitor (line 192) | type NatHoleVisitor struct
  type NatHoleClient (line 203) | type NatHoleClient struct
  type PortsRange (line 211) | type PortsRange struct
  type NatHoleDetectBehavior (line 216) | type NatHoleDetectBehavior struct
  type NatHoleResp (line 227) | type NatHoleResp struct
  type NatHoleSid (line 237) | type NatHoleSid struct
  type NatHoleReport (line 244) | type NatHoleReport struct

FILE: pkg/naming/names.go
  function AddUserPrefix (line 6) | func AddUserPrefix(user, name string) string {
  function StripUserPrefix (line 15) | func StripUserPrefix(user, name string) string {
  function BuildTargetServerProxyName (line 27) | func BuildTargetServerProxyName(localUser, serverUser, serverName string...

FILE: pkg/naming/names_test.go
  function TestAddUserPrefix (line 9) | func TestAddUserPrefix(t *testing.T) {
  function TestStripUserPrefix (line 15) | func TestStripUserPrefix(t *testing.T) {
  function TestBuildTargetServerProxyName (line 23) | func TestBuildTargetServerProxyName(t *testing.T) {

FILE: pkg/nathole/analysis.go
  function getBehaviorByMode (line 122) | func getBehaviorByMode(mode int) []lo.Tuple2[RecommandBehavior, Recomman...
  function getBehaviorByModeAndIndex (line 139) | func getBehaviorByModeAndIndex(mode int, index int) (RecommandBehavior, ...
  function getBehaviorScoresByMode (line 147) | func getBehaviorScoresByMode(mode int, defaultScore int) []*BehaviorScore {
  function getBehaviorScoresByMode2 (line 151) | func getBehaviorScoresByMode2(mode int, senderScore, receiverScore int) ...
  type RecommandBehavior (line 164) | type RecommandBehavior struct
  type MakeHoleRecords (line 173) | type MakeHoleRecords struct
    method ReportSuccess (line 218) | func (mhr *MakeHoleRecords) ReportSuccess(mode int, index int) {
    method Recommand (line 234) | func (mhr *MakeHoleRecords) Recommand() (mode, index int) {
  function NewMakeHoleRecords (line 179) | func NewMakeHoleRecords(c, v *NatFeature) *MakeHoleRecords {
  type BehaviorScore (line 249) | type BehaviorScore struct
  type Analyzer (line 256) | type Analyzer struct
    method GetRecommandBehaviors (line 271) | func (a *Analyzer) GetRecommandBehaviors(key string, c, v *NatFeature)...
    method ReportSuccess (line 303) | func (a *Analyzer) ReportSuccess(key string, mode, index int) {
    method Clean (line 313) | func (a *Analyzer) Clean() (int, int) {
  function NewAnalyzer (line 264) | func NewAnalyzer(dataReserveDuration time.Duration) *Analyzer {

FILE: pkg/nathole/classify.go
  constant EasyNAT (line 25) | EasyNAT = "EasyNAT"
  constant HardNAT (line 26) | HardNAT = "HardNAT"
  constant BehaviorNoChange (line 28) | BehaviorNoChange    = "BehaviorNoChange"
  constant BehaviorIPChanged (line 29) | BehaviorIPChanged   = "BehaviorIPChanged"
  constant BehaviorPortChanged (line 30) | BehaviorPortChanged = "BehaviorPortChanged"
  constant BehaviorBothChanged (line 31) | BehaviorBothChanged = "BehaviorBothChanged"
  type NatFeature (line 34) | type NatFeature struct
  function ClassifyNATFeature (line 42) | func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeat...
  function ClassifyFeatureCount (line 106) | func ClassifyFeatureCount(features []*NatFeature) (int, int, int) {

FILE: pkg/nathole/controller.go
  function NewTransactionID (line 41) | func NewTransactionID() string {
  type ClientCfg (line 46) | type ClientCfg struct
  type Session (line 53) | type Session struct
    method genAnalysisKey (line 74) | func (s *Session) genAnalysisKey() {
  type Controller (line 94) | type Controller struct
    method CleanWorker (line 110) | func (c *Controller) CleanWorker(ctx context.Context) {
    method ListenClient (line 125) | func (c *Controller) ListenClient(name string, sk string, allowUsers [...
    method CloseClient (line 141) | func (c *Controller) CloseClient(name string) {
    method GenSid (line 147) | func (c *Controller) GenSid() string {
    method HandleVisitor (line 153) | func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter ...
    method HandleClient (line 255) | func (c *Controller) HandleClient(m *msg.NatHoleClient, transporter tr...
    method HandleReport (line 271) | func (c *Controller) HandleReport(m *msg.NatHoleReport) {
    method GenNatHoleResponse (line 286) | func (c *Controller) GenNatHoleResponse(transactionID string, session ...
    method analysis (line 300) | func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *ms...
  function NewController (line 102) | func NewController(analysisDataReserveDuration time.Duration) (*Controll...
  function getRangePorts (line 371) | func getRangePorts(addrs []string, difference, maxNumber int) []msg.Port...

FILE: pkg/nathole/discovery.go
  type Message (line 27) | type Message struct
  function Discover (line 33) | func Discover(stunServers []string, localAddr string) ([]string, net.Add...
  type stunResponse (line 55) | type stunResponse struct
  type discoverConn (line 60) | type discoverConn struct
    method Close (line 88) | func (c *discoverConn) Close() error {
    method readLoop (line 96) | func (c *discoverConn) readLoop() {
    method doSTUNRequest (line 112) | func (c *discoverConn) doSTUNRequest(addr string) (*stunResponse, erro...
    method discoverFromStunServer (line 160) | func (c *discoverConn) discoverFromStunServer(addr string) ([]string, ...
  function listen (line 67) | func listen(localAddr string) (*discoverConn, error) {

FILE: pkg/nathole/nathole.go
  type PrepareOptions (line 72) | type PrepareOptions struct
  type PrepareResult (line 78) | type PrepareResult struct
  function PreCheck (line 88) | func PreCheck(
  function Prepare (line 118) | func Prepare(stunServers []string, opts PrepareOptions) (*PrepareResult,...
  function ExchangeInfo (line 164) | func ExchangeInfo(
  function MakeHole (line 192) | func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHo...
  function waitDetectMessage (line 290) | func waitDetectMessage(
  function sendSidMessage (line 336) | func sendSidMessage(
  function sendSidMessageToRangePorts (line 388) | func sendSidMessageToRangePorts(
  function sendSidMessageToRandomPorts (line 406) | func sendSidMessageToRandomPorts(
  function parseIPs (line 445) | func parseIPs(addrs []string) []string {

FILE: pkg/nathole/utils.go
  function EncodeMessage (line 29) | func EncodeMessage(m msg.Message, key []byte) ([]byte, error) {
  function DecodeMessageInto (line 42) | func DecodeMessageInto(data, key []byte, m msg.Message) error {
  type ChangedAddress (line 51) | type ChangedAddress struct
    method GetFrom (line 56) | func (s *ChangedAddress) GetFrom(m *stun.Message) error {
    method String (line 61) | func (s *ChangedAddress) String() string {
  function ListAllLocalIPs (line 65) | func ListAllLocalIPs() ([]net.IP, error) {
  function ListLocalIPsForNatHole (line 81) | func ListLocalIPsForNatHole(maxItems int) ([]string, error) {

FILE: pkg/plugin/client/http2http.go
  function init (line 33) | func init() {
  type HTTP2HTTPPlugin (line 37) | type HTTP2HTTPPlugin struct
    method Handle (line 82) | func (p *HTTP2HTTPPlugin) Handle(_ context.Context, connInfo *Connecti...
    method Name (line 87) | func (p *HTTP2HTTPPlugin) Name() string {
    method Close (line 91) | func (p *HTTP2HTTPPlugin) Close() error {
  function NewHTTP2HTTPPlugin (line 44) | func NewHTTP2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions)...

FILE: pkg/plugin/client/http2https.go
  function init (line 34) | func init() {
  type HTTP2HTTPSPlugin (line 38) | type HTTP2HTTPSPlugin struct
    method Handle (line 91) | func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, connInfo *Connect...
    method Name (line 96) | func (p *HTTP2HTTPSPlugin) Name() string {
    method Close (line 100) | func (p *HTTP2HTTPSPlugin) Close() error {
  function NewHTTP2HTTPSPlugin (line 45) | func NewHTTP2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions...

FILE: pkg/plugin/client/http_proxy.go
  function init (line 37) | func init() {
  type HTTPProxy (line 41) | type HTTPProxy struct
    method Name (line 68) | func (hp *HTTPProxy) Name() string {
    method Handle (line 72) | func (hp *HTTPProxy) Handle(_ context.Context, connInfo *ConnectionInf...
    method Close (line 97) | func (hp *HTTPProxy) Close() error {
    method ServeHTTP (line 103) | func (hp *HTTPProxy) ServeHTTP(rw http.ResponseWriter, req *http.Reque...
    method HTTPHandler (line 119) | func (hp *HTTPProxy) HTTPHandler(rw http.ResponseWriter, req *http.Req...
    method ConnectHandler (line 141) | func (hp *HTTPProxy) ConnectHandler(rw http.ResponseWriter, req *http....
    method Auth (line 165) | func (hp *HTTPProxy) Auth(req *http.Request) bool {
    method handleConnectReq (line 193) | func (hp *HTTPProxy) handleConnectReq(req *http.Request, rwc io.ReadWr...
  function NewHTTPProxyPlugin (line 48) | func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions)...
  function copyHeaders (line 220) | func copyHeaders(dst, src http.Header) {
  function removeProxyHeaders (line 228) | func removeProxyHeaders(req *http.Request) {
  function getBadResponse (line 240) | func getBadResponse() *http.Response {

FILE: pkg/plugin/client/https2http.go
  function init (line 38) | func init() {
  type HTTPS2HTTPPlugin (line 42) | type HTTPS2HTTPPlugin struct
    method Handle (line 107) | func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, connInfo *Connect...
    method Name (line 115) | func (p *HTTPS2HTTPPlugin) Name() string {
    method Close (line 119) | func (p *HTTPS2HTTPPlugin) Close() error {
  function NewHTTPS2HTTPPlugin (line 49) | func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions...

FILE: pkg/plugin/client/https2https.go
  function init (line 38) | func init() {
  type HTTPS2HTTPSPlugin (line 42) | type HTTPS2HTTPSPlugin struct
    method Handle (line 113) | func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, connInfo *Connec...
    method Name (line 121) | func (p *HTTPS2HTTPSPlugin) Name() string {
    method Close (line 125) | func (p *HTTPS2HTTPSPlugin) Close() error {
  function NewHTTPS2HTTPSPlugin (line 49) | func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOption...

FILE: pkg/plugin/client/plugin.go
  type PluginContext (line 31) | type PluginContext struct
  type CreatorFn (line 39) | type CreatorFn
  function Register (line 41) | func Register(name string, fn CreatorFn) {
  function Create (line 48) | func Create(pluginName string, pluginCtx PluginContext, options v1.Clien...
  type ConnectionInfo (line 57) | type ConnectionInfo struct
  type Plugin (line 66) | type Plugin interface
  type Listener (line 73) | type Listener struct
    method Accept (line 85) | func (l *Listener) Accept() (net.Conn, error) {
    method PutConn (line 93) | func (l *Listener) PutConn(conn net.Conn) error {
    method Close (line 100) | func (l *Listener) Close() error {
    method Addr (line 110) | func (l *Listener) Addr() net.Addr {
  function NewProxyListener (line 79) | func NewProxyListener() *Listener {

FILE: pkg/plugin/client/socks5.go
  function init (line 30) | func init() {
  type Socks5Plugin (line 34) | type Socks5Plugin struct
    method Handle (line 53) | func (sp *Socks5Plugin) Handle(_ context.Context, connInfo *Connection...
    method Name (line 59) | func (sp *Socks5Plugin) Name() string {
    method Close (line 63) | func (sp *Socks5Plugin) Close() error {
  function NewSocks5Plugin (line 38) | func NewSocks5Plugin(_ PluginContext, options v1.ClientPluginOptions) (p...

FILE: pkg/plugin/client/static_file.go
  function init (line 30) | func init() {
  type StaticFilePlugin (line 34) | type StaticFilePlugin struct
    method Handle (line 71) | func (sp *StaticFilePlugin) Handle(_ context.Context, connInfo *Connec...
    method Name (line 76) | func (sp *StaticFilePlugin) Name() string {
    method Close (line 80) | func (sp *StaticFilePlugin) Close() error {
  function NewStaticFilePlugin (line 41) | func NewStaticFilePlugin(_ PluginContext, options v1.ClientPluginOptions...

FILE: pkg/plugin/client/tls2raw.go
  function init (line 32) | func init() {
  type TLS2RawPlugin (line 36) | type TLS2RawPlugin struct
    method Handle (line 57) | func (p *TLS2RawPlugin) Handle(ctx context.Context, connInfo *Connecti...
    method Name (line 78) | func (p *TLS2RawPlugin) Name() string {
    method Close (line 82) | func (p *TLS2RawPlugin) Close() error {
  function NewTLS2RawPlugin (line 42) | func NewTLS2RawPlugin(_ PluginContext, options v1.ClientPluginOptions) (...

FILE: pkg/plugin/client/unix_domain_socket.go
  function init (line 29) | func init() {
  type UnixDomainSocketPlugin (line 33) | type UnixDomainSocketPlugin struct
    method Handle (line 52) | func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, connInf...
    method Name (line 71) | func (uds *UnixDomainSocketPlugin) Name() string {
    method Close (line 75) | func (uds *UnixDomainSocketPlugin) Close() error {
  function NewUnixDomainSocketPlugin (line 37) | func NewUnixDomainSocketPlugin(_ PluginContext, options v1.ClientPluginO...

FILE: pkg/plugin/client/virtual_net.go
  function init (line 27) | func init() {
  type VirtualNetPlugin (line 31) | type VirtualNetPlugin struct
    method Handle (line 48) | func (p *VirtualNetPlugin) Handle(ctx context.Context, connInfo *Conne...
    method RemoveConn (line 69) | func (p *VirtualNetPlugin) RemoveConn(conn io.ReadWriteCloser) {
    method Name (line 78) | func (p *VirtualNetPlugin) Name() string {
    method Close (line 82) | func (p *VirtualNetPlugin) Close() error {
  function NewVirtualNetPlugin (line 38) | func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.ClientPlugi...

FILE: pkg/plugin/server/http.go
  type httpPlugin (line 33) | type httpPlugin struct
    method Name (line 63) | func (p *httpPlugin) Name() string {
    method IsSupport (line 67) | func (p *httpPlugin) IsSupport(op string) bool {
    method Handle (line 71) | func (p *httpPlugin) Handle(ctx context.Context, op string, content an...
    method do (line 85) | func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response...
  function NewHTTPPluginOptions (line 40) | func NewHTTPPluginOptions(options v1.HTTPPluginOptions) Plugin {

FILE: pkg/plugin/server/manager.go
  type Manager (line 27) | type Manager struct
    method Register (line 47) | func (m *Manager) Register(p Plugin) {
    method Login (line 68) | func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
    method NewProxy (line 102) | func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent...
    method CloseProxy (line 136) | func (m *Manager) CloseProxy(content *CloseProxyContent) error {
    method Ping (line 161) | func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
    method NewWorkConn (line 195) | func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkCo...
    method NewUserConn (line 229) | func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserCo...
  function NewManager (line 36) | func NewManager() *Manager {

FILE: pkg/plugin/server/plugin.go
  constant APIVersion (line 22) | APIVersion = "0.1.0"
  constant OpLogin (line 24) | OpLogin       = "Login"
  constant OpNewProxy (line 25) | OpNewProxy    = "NewProxy"
  constant OpCloseProxy (line 26) | OpCloseProxy  = "CloseProxy"
  constant OpPing (line 27) | OpPing        = "Ping"
  constant OpNewWorkConn (line 28) | OpNewWorkConn = "NewWorkConn"
  constant OpNewUserConn (line 29) | OpNewUserConn = "NewUserConn"
  type Plugin (line 32) | type Plugin interface

FILE: pkg/plugin/server/tracer.go
  type key (line 21) | type key
  constant reqidKey (line 24) | reqidKey key = 0
  function NewReqidContext (line 27) | func NewReqidContext(ctx context.Context, reqid string) context.Context {
  function GetReqidFromContext (line 31) | func GetReqidFromContext(ctx context.Context) string {

FILE: pkg/plugin/server/types.go
  type Request (line 21) | type Request struct
  type Response (line 27) | type Response struct
  type LoginContent (line 34) | type LoginContent struct
  type UserInfo (line 40) | type UserInfo struct
  type NewProxyContent (line 46) | type NewProxyContent struct
  type CloseProxyContent (line 51) | type CloseProxyContent struct
  type PingContent (line 56) | type PingContent struct
  type NewWorkConnContent (line 61) | type NewWorkConnContent struct
  type NewUserConnContent (line 66) | type NewUserConnContent struct

FILE: pkg/plugin/visitor/plugin.go
  type PluginContext (line 27) | type PluginContext struct
  type CreatorFn (line 45) | type CreatorFn
  function Register (line 47) | func Register(name string, fn CreatorFn) {
  function Create (line 54) | func Create(pluginName string, pluginCtx PluginContext, options v1.Visit...
  type Plugin (line 63) | type Plugin interface

FILE: pkg/plugin/visitor/virtual_net.go
  function init (line 32) | func init() {
  type VirtualNetPlugin (line 36) | type VirtualNetPlugin struct
    method Name (line 82) | func (p *VirtualNetPlugin) Name() string {
    method Start (line 86) | func (p *VirtualNetPlugin) Start() {
    method run (line 101) | func (p *VirtualNetPlugin) run() {
    method cleanupControllerConn (line 186) | func (p *VirtualNetPlugin) cleanupControllerConn(xl *xlog.Logger) {
    method Close (line 198) | func (p *VirtualNetPlugin) Close() error {
  function NewVirtualNetPlugin (line 51) | func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.VisitorPlug...

FILE: pkg/policy/featuregate/feature_gate.go
  type Feature (line 27) | type Feature
  type FeatureStage (line 30) | type FeatureStage
  constant Alpha (line 34) | Alpha FeatureStage = "ALPHA"
  constant Beta (line 36) | Beta FeatureStage = "BETA"
  constant GA (line 38) | GA FeatureStage = ""
  type FeatureSpec (line 42) | type FeatureSpec struct
  type FeatureGate (line 63) | type FeatureGate interface
  type MutableFeatureGate (line 71) | type MutableFeatureGate interface
  type featureGate (line 83) | type featureGate struct
    method SetFromMap (line 105) | func (f *featureGate) SetFromMap(m map[string]bool) error {
    method Add (line 133) | func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
    method String (line 162) | func (f *featureGate) String() string {
    method Enabled (line 173) | func (f *featureGate) Enabled(key Feature) bool {
    method KnownFeatures (line 185) | func (f *featureGate) KnownFeatures() []string {
  function NewFeatureGate (line 95) | func NewFeatureGate() MutableFeatureGate {
  function Enabled (line 202) | func Enabled(name Feature) bool {
  function SetFromMap (line 207) | func SetFromMap(featureMap map[string]bool) error {

FILE: pkg/policy/security/unsafe.go
  constant TokenSourceExec (line 4) | TokenSourceExec = "TokenSourceExec"
  type UnsafeFeatures (line 17) | type UnsafeFeatures struct
    method IsEnabled (line 29) | func (u *UnsafeFeatures) IsEnabled(feature string) bool {
  function NewUnsafeFeatures (line 21) | func NewUnsafeFeatures(allowed []string) *UnsafeFeatures {

FILE: pkg/proto/udp/udp.go
  function NewUDPPacket (line 29) | func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket {
  function GetContent (line 39) | func GetContent(m *msg.UDPPacket) (buf []byte, err error) {
  function ForwardUserConn (line 43) | func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UDPPacket,...
  function Forwarder (line 73) | func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendC...

FILE: pkg/proto/udp/udp_test.go
  function TestUdpPacket (line 9) | func TestUdpPacket(t *testing.T) {

FILE: pkg/sdk/client/client.go
  type Client (line 18) | type Client struct
    method SetAuth (line 30) | func (c *Client) SetAuth(user, pwd string) {
    method GetProxyStatus (line 35) | func (c *Client) GetProxyStatus(ctx context.Context, name string) (*mo...
    method GetAllProxyStatus (line 58) | func (c *Client) GetAllProxyStatus(ctx context.Context) (model.StatusR...
    method Reload (line 74) | func (c *Client) Reload(ctx context.Context, strictMode bool) error {
    method Stop (line 91) | func (c *Client) Stop(ctx context.Context) error {
    method GetConfig (line 100) | func (c *Client) GetConfig(ctx context.Context) (string, error) {
    method UpdateConfig (line 108) | func (c *Client) UpdateConfig(ctx context.Context, content string) err...
    method setAuthHeader (line 117) | func (c *Client) setAuthHeader(req *http.Request) {
    method do (line 123) | func (c *Client) do(req *http.Request) (string, error) {
  function New (line 24) | func New(host string, port int) *Client {

FILE: pkg/ssh/gateway.go
  type Gateway (line 32) | type Gateway struct
    method Run (line 105) | func (g *Gateway) Run() {
    method Close (line 115) | func (g *Gateway) Close() error {
    method handleConn (line 119) | func (g *Gateway) handleConn(conn net.Conn) {
  function NewGateway (line 41) | func NewGateway(
  function loadAuthorizedKeysFromFile (line 131) | func loadAuthorizedKeysFromFile(path string) (map[string]string, error) {

FILE: pkg/ssh/server.go
  constant ChannelTypeServerOpenChannel (line 46) | ChannelTypeServerOpenChannel = "forwarded-tcpip"
  constant RequestTypeForward (line 47) | RequestTypeForward           = "tcpip-forward"
  type tcpipForward (line 50) | type tcpipForward struct
  type forwardedTCPPayload (line 56) | type forwardedTCPPayload struct
  type TunnelServer (line 64) | type TunnelServer struct
    method Run (line 86) | func (s *TunnelServer) Run() error {
    method writeToClient (line 189) | func (s *TunnelServer) writeToClient(data string) {
    method waitForwardAddrAndExtraPayload (line 196) | func (s *TunnelServer) waitForwardAddrAndExtraPayload(
    method parseClientAndProxyConfigurer (line 253) | func (s *TunnelServer) parseClientAndProxyConfigurer(_ *tcpipForward, ...
    method handleNewChannel (line 298) | func (s *TunnelServer) handleNewChannel(channel ssh.NewChannel, extraP...
    method keepAlive (line 327) | func (s *TunnelServer) keepAlive(ch ssh.Channel) {
    method openConn (line 344) | func (s *TunnelServer) openConn(addr *tcpipForward) (net.Conn, error) {
    method waitProxyStatusReady (line 362) | func (s *TunnelServer) waitProxyStatusReady(name string, timeout time....
  function NewTunnelServer (line 76) | func NewTunnelServer(conn net.Conn, sc *ssh.ServerConfig, peerServerList...

FILE: pkg/ssh/terminal.go
  function createSuccessInfo (line 22) | func createSuccessInfo(user string, pc v1.ProxyConfigurer, ps *proxy.Wor...

FILE: pkg/transport/message.go
  type MessageTransporter (line 27) | type MessageTransporter interface
  type MessageSender (line 38) | type MessageSender interface
  function NewMessageTransporter (line 42) | func NewMessageTransporter(sender MessageSender) MessageTransporter {
  type transporterImpl (line 49) | type transporterImpl struct
    method Send (line 59) | func (impl *transporterImpl) Send(m msg.Message) error {
    method Do (line 63) | func (impl *transporterImpl) Do(ctx context.Context, req msg.Message, ...
    method DispatchWithType (line 81) | func (impl *transporterImpl) DispatchWithType(m msg.Message, msgType, ...
    method Dispatch (line 102) | func (impl *transporterImpl) Dispatch(m msg.Message, laneKey string) b...
    method registerMsgChan (line 107) | func (impl *transporterImpl) registerMsgChan(recvCh chan msg.Message, ...

FILE: pkg/transport/tls.go
  function newCustomTLSKeyPair (line 29) | func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, er...
  function newRandomTLSKeyPair (line 37) | func newRandomTLSKeyPair() (*tls.Certificate, error) {
  function newCertPool (line 81) | func newCertPool(caPath string) (*x509.CertPool, error) {
  function NewServerTLSConfig (line 96) | func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, ...
  function NewClientTLSConfig (line 128) | func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*...
  function NewRandomPrivateKey (line 157) | func NewRandomPrivateKey() ([]byte, error) {

FILE: pkg/util/http/context.go
  type Context (line 25) | type Context struct
    method Param (line 39) | func (c *Context) Param(key string) string {
    method Query (line 43) | func (c *Context) Query(key string) string {
    method BindJSON (line 47) | func (c *Context) BindJSON(obj any) error {
    method Body (line 55) | func (c *Context) Body() ([]byte, error) {
  function NewContext (line 31) | func NewContext(w http.ResponseWriter, r *http.Request) *Context {

FILE: pkg/util/http/error.go
  type Error (line 19) | type Error struct
    method Error (line 24) | func (e *Error) Error() string {
  function NewError (line 28) | func NewError(code int, msg string) *Error {

FILE: pkg/util/http/handler.go
  type GeneralResponse (line 24) | type GeneralResponse struct
  type APIHandler (line 30) | type APIHandler
  function MakeHTTPHandlerFunc (line 33) | func MakeHTTPHandlerFunc(handler APIHandler) http.HandlerFunc {

FILE: pkg/util/http/http.go
  function OkResponse (line 24) | func OkResponse() *http.Response {
  function ProxyUnauthorizedResponse (line 38) | func ProxyUnauthorizedResponse() *http.Response {
  function CanonicalHost (line 54) | func CanonicalHost(host string) (string, error) {
  function hasPort (line 70) | func hasPort(host string) bool {
  function ParseBasicAuth (line 81) | func ParseBasicAuth(auth string) (username, password string, ok bool) {
  function BasicAuth (line 99) | func BasicAuth(username, passwd string) string {

FILE: pkg/util/http/middleware.go
  type responseWriter (line 23) | type responseWriter struct
    method WriteHeader (line 28) | func (rw *responseWriter) WriteHeader(code int) {
  function NewRequestLogger (line 33) | func NewRequestLogger(next http.Handler) http.Handler {

FILE: pkg/util/http/server.go
  type Server (line 37) | type Server struct
    method Address (line 90) | func (s *Server) Address() string {
    method Run (line 94) | func (s *Server) Run() error {
    method Close (line 102) | func (s *Server) Close() error {
    method RouteRegister (line 116) | func (s *Server) RouteRegister(register func(helper *RouterRegisterHel...
    method registerPprofHandlers (line 124) | func (s *Server) registerPprofHandlers() {
  function NewServer (line 48) | func NewServer(cfg v1.WebServerConfig) (*Server, error) {
  type RouterRegisterHelper (line 110) | type RouterRegisterHelper struct

FILE: pkg/util/jsonx/json_v1.go
  type DecodeOptions (line 22) | type DecodeOptions struct
  function Marshal (line 26) | func Marshal(v any) ([]byte, error) {
  function MarshalIndent (line 30) | func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
  function Unmarshal (line 34) | func Unmarshal(data []byte, out any) error {
  function UnmarshalWithOptions (line 38) | func UnmarshalWithOptions(data []byte, out any, options DecodeOptions) e...

FILE: pkg/util/jsonx/raw_message.go
  type RawMessage (line 21) | type RawMessage
    method MarshalJSON (line 23) | func (m RawMessage) MarshalJSON() ([]byte, error) {
    method UnmarshalJSON (line 30) | func (m *RawMessage) UnmarshalJSON(data []byte) error {

FILE: pkg/util/limit/reader.go
  type Reader (line 24) | type Reader struct
    method Read (line 36) | func (r *Reader) Read(p []byte) (n int, err error) {
  function NewReader (line 29) | func NewReader(r io.Reader, limiter *rate.Limiter) *Reader {

FILE: pkg/util/limit/writer.go
  type Writer (line 24) | type Writer struct
    method Write (line 36) | func (w *Writer) Write(p []byte) (n int, err error) {
  function NewWriter (line 29) | func NewWriter(w io.Writer, limiter *rate.Limiter) *Writer {

FILE: pkg/util/log/log.go
  function init (line 34) | func init() {
  function InitLogger (line 42) | func InitLogger(logPath string, levelStr string, maxDays int, disableLog...
  function Errorf (line 70) | func Errorf(format string, v ...any) {
  function Warnf (line 74) | func Warnf(format string, v ...any) {
  function Infof (line 78) | func Infof(format string, v ...any) {
  function Debugf (line 82) | func Debugf(format string, v ...any) {
  function Tracef (line 86) | func Tracef(format string, v ...any) {
  function Logf (line 90) | func Logf(level log.Level, offset int, format string, v ...any) {
  type WriteLogger (line 94) | type WriteLogger struct
    method Write (line 106) | func (w *WriteLogger) Write(p []byte) (n int, err error) {
  function NewWriteLogger (line 99) | func NewWriteLogger(level log.Level, offset int) *WriteLogger {

FILE: pkg/util/metric/counter.go
  type Counter (line 21) | type Counter interface
  function NewCounter (line 29) | func NewCounter() Counter {
  type StandardCounter (line 35) | type StandardCounter struct
    method Count (line 39) | func (c *StandardCounter) Count() int32 {
    method Inc (line 43) | func (c *StandardCounter) Inc(count int32) {
    method Dec (line 47) | func (c *StandardCounter) Dec(count int32) {
    method Snapshot (line 51) | func (c *StandardCounter) Snapshot() Counter {
    method Clear (line 58) | func (c *StandardCounter) Clear() {

FILE: pkg/util/metric/counter_test.go
  function TestCounter (line 9) | func TestCounter(t *testing.T) {

FILE: pkg/util/metric/date_counter.go
  type DateCounter (line 22) | type DateCounter interface
  function NewDateCounter (line 31) | func NewDateCounter(reserveDays int64) DateCounter {
  type StandardDateCounter (line 38) | type StandardDateCounter struct
    method TodayCount (line 57) | func (c *StandardDateCounter) TodayCount() int64 {
    method GetLastDaysCount (line 65) | func (c *StandardDateCounter) GetLastDaysCount(lastdays int64) []int64 {
    method Inc (line 80) | func (c *StandardDateCounter) Inc(count int64) {
    method Dec (line 87) | func (c *StandardDateCounter) Dec(count int64) {
    method Snapshot (line 94) | func (c *StandardDateCounter) Snapshot() DateCounter {
    method Clear (line 104) | func (c *StandardDateCounter) Clear() {
    method rotate (line 114) | func (c *StandardDateCounter) rotate(now time.Time) {
  function newStandardDateCounter (line 46) | func newStandardDateCounter(reserveDays int64) *StandardDateCounter {

FILE: pkg/util/metric/date_counter_test.go
  function TestDateCounter (line 9) | func TestDateCounter(t *testing.T) {

FILE: pkg/util/metric/metrics.go
  type GaugeMetric (line 19) | type GaugeMetric interface
  type CounterMetric (line 27) | type CounterMetric interface
  type HistogramMetric (line 32) | type HistogramMetric interface

FILE: pkg/util/net/conn.go
  type ContextGetter (line 31) | type ContextGetter interface
  type ContextSetter (line 35) | type ContextSetter interface
  function NewLogFromConn (line 39) | func NewLogFromConn(conn net.Conn) *xlog.Logger {
  function NewContextFromConn (line 46) | func NewContextFromConn(conn net.Conn) context.Context {
  type ContextConn (line 54) | type ContextConn struct
    method WithContext (line 67) | func (c *ContextConn) WithContext(ctx context.Context) {
    method Context (line 71) | func (c *ContextConn) Context() context.Context {
  function NewContextConn (line 60) | func NewContextConn(ctx context.Context, c net.Conn) *ContextConn {
  type WrapReadWriteCloserConn (line 75) | type WrapReadWriteCloserConn struct
    method LocalAddr (line 90) | func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
    method SetRemoteAddr (line 97) | func (conn *WrapReadWriteCloserConn) SetRemoteAddr(addr net.Addr) {
    method RemoteAddr (line 101) | func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
    method SetDeadline (line 111) | func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {
    method SetReadDeadline (line 118) | func (conn *WrapReadWriteCloserConn) SetReadDeadline(t time.Time) error {
    method SetWriteDeadline (line 125) | func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) err...
  function WrapReadWriteCloserToConn (line 83) | func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Con...
  type CloseNotifyConn (line 132) | type CloseNotifyConn struct
    method Close (line 149) | func (cc *CloseNotifyConn) Close() (err error) {
    method CloseWithError (line 161) | func (cc *CloseNotifyConn) CloseWithError(err error) error {
  function WrapCloseNotifyConn (line 142) | func WrapCloseNotifyConn(c net.Conn, closeFn func(error)) *CloseNotifyCo...
  type StatsConn (line 173) | type StatsConn struct
    method Read (line 189) | func (statsConn *StatsConn) Read(p []byte) (n int, err error) {
    method Write (line 195) | func (statsConn *StatsConn) Write(p []byte) (n int, err error) {
    method Close (line 201) | func (statsConn *StatsConn) Close() (err error) {
  function WrapStatsConn (line 182) | func WrapStatsConn(conn net.Conn, statsFunc func(total, totalWrite int64...
  type wrapQuicStream (line 212) | type wrapQuicStream struct
    method LocalAddr (line 224) | func (conn *wrapQuicStream) LocalAddr() net.Addr {
    method RemoteAddr (line 231) | func (conn *wrapQuicStream) RemoteAddr() net.Addr {
    method Close (line 238) | func (conn *wrapQuicStream) Close() error {
  function QuicStreamToNetConn (line 217) | func QuicStreamToNetConn(s *quic.Stream, c *quic.Conn) net.Conn {
  function NewCryptoReadWriter (line 243) | func NewCryptoReadWriter(rw io.ReadWriter, key []byte) (io.ReadWriter, e...

FILE: pkg/util/net/dial.go
  function DialHookCustomTLSHeadByte (line 12) | func DialHookCustomTLSHeadByte(enableTLS bool, disableCustomTLSHeadByte ...
  function DialHookWebsocket (line 24) | func DialHookWebsocket(protocol string, host string) libnet.AfterHookFunc {

FILE: pkg/util/net/dns.go
  function SetDefaultDNSAddress (line 22) | func SetDefaultDNSAddress(dnsAddress string) {

FILE: pkg/util/net/http.go
  type HTTPAuthMiddleware (line 27) | type HTTPAuthMiddleware struct
    method SetAuthFailDelay (line 40) | func (authMid *HTTPAuthMiddleware) SetAuthFailDelay(delay time.Duratio...
    method Middleware (line 45) | func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http....
  function NewHTTPAuthMiddleware (line 33) | func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware {
  type HTTPGzipWrapper (line 62) | type HTTPGzipWrapper struct
    method ServeHTTP (line 66) | func (gw *HTTPGzipWrapper) ServeHTTP(w http.ResponseWriter, r *http.Re...
  function MakeHTTPGzipHandler (line 78) | func MakeHTTPGzipHandler(h http.Handler) http.Handler {
  type gzipResponseWriter (line 84) | type gzipResponseWriter struct
    method Write (line 89) | func (w gzipResponseWriter) Write(b []byte) (int, error) {

FILE: pkg/util/net/kcp.go
  type KCPListener (line 24) | type KCPListener struct
    method Accept (line 67) | func (l *KCPListener) Accept() (net.Conn, error) {
    method Close (line 75) | func (l *KCPListener) Close() error {
    method Addr (line 83) | func (l *KCPListener) Addr() net.Addr {
  function ListenKcp (line 30) | func ListenKcp(address string) (l *KCPListener, err error) {
  function NewKCPConnFromUDP (line 87) | func NewKCPConnFromUDP(conn *net.UDPConn, connected bool, raddr string) ...

FILE: pkg/util/net/listener.go
  type InternalListener (line 27) | type InternalListener struct
    method Accept (line 39) | func (l *InternalListener) Accept() (net.Conn, error) {
    method PutConn (line 47) | func (l *InternalListener) PutConn(conn net.Conn) error {
    method Close (line 61) | func (l *InternalListener) Close() error {
    method Addr (line 71) | func (l *InternalListener) Addr() net.Addr {
  function NewInternalListener (line 33) | func NewInternalListener() *InternalListener {
  type InternalAddr (line 75) | type InternalAddr struct
    method Network (line 77) | func (ia *InternalAddr) Network() string {
    method String (line 81) | func (ia *InternalAddr) String() string {

FILE: pkg/util/net/proxyprotocol.go
  function BuildProxyProtocolHeaderStruct (line 25) | func BuildProxyProtocolHeaderStruct(srcAddr, dstAddr net.Addr, version s...
  function BuildProxyProtocolHeader (line 35) | func BuildProxyProtocolHeader(srcAddr, dstAddr net.Addr, version string)...

FILE: pkg/util/net/proxyprotocol_test.go
  function TestBuildProxyProtocolHeader (line 11) | func TestBuildProxyProtocolHeader(t *testing.T) {
  function TestBuildProxyProtocolHeaderStruct (line 85) | func TestBuildProxyProtocolHeaderStruct(t *testing.T) {

FILE: pkg/util/net/tls.go
  function CheckAndEnableTLSServerConnWithTimeout (line 28) | func CheckAndEnableTLSServerConnWithTimeout(

FILE: pkg/util/net/udp.go
  type UDPPacket (line 28) | type UDPPacket struct
  type FakeUDPConn (line 34) | type FakeUDPConn struct
    method putPacket (line 69) | func (c *FakeUDPConn) putPacket(content []byte) {
    method Read (line 80) | func (c *FakeUDPConn) Read(b []byte) (n int, err error) {
    method Write (line 94) | func (c *FakeUDPConn) Write(b []byte) (n int, err error) {
    method Close (line 115) | func (c *FakeUDPConn) Close() error {
    method IsClosed (line 125) | func (c *FakeUDPConn) IsClosed() bool {
    method LocalAddr (line 131) | func (c *FakeUDPConn) LocalAddr() net.Addr {
    method RemoteAddr (line 135) | func (c *FakeUDPConn) RemoteAddr() net.Addr {
    method SetDeadline (line 139) | func (c *FakeUDPConn) SetDeadline(_ time.Time) error {
    method SetReadDeadline (line 143) | func (c *FakeUDPConn) SetReadDeadline(_ time.Time) error {
    method SetWriteDeadline (line 147) | func (c *FakeUDPConn) SetWriteDeadline(_ time.Time) error {
  function NewFakeUDPConn (line 46) | func NewFakeUDPConn(l *UDPListener, laddr, raddr net.Addr) *FakeUDPConn {
  type UDPListener (line 151) | type UDPListener struct
    method writeUDPPacket (line 218) | func (l *UDPListener) writeUDPPacket(packet *UDPPacket) (err error) {
    method WriteMsg (line 228) | func (l *UDPListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (e...
    method Accept (line 238) | func (l *UDPListener) Accept() (net.Conn, error) {
    method Close (line 246) | func (l *UDPListener) Close() error {
    method Addr (line 256) | func (l *UDPListener) Addr() net.Addr {
  function ListenUDP (line 161) | func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
  type ConnectedUDPConn (line 263) | type ConnectedUDPConn struct
    method WriteTo (line 266) | func (c *ConnectedUDPConn) WriteTo(b []byte, _ net.Addr) (int, error) ...

FILE: pkg/util/net/websocket.go
  constant FrpWebsocketPath (line 15) | FrpWebsocketPath = "/~!frp"
  type WebsocketListener (line 18) | type WebsocketListener struct
    method Accept (line 55) | func (p *WebsocketListener) Accept() (net.Conn, error) {
    method Close (line 63) | func (p *WebsocketListener) Close() error {
    method Addr (line 67) | func (p *WebsocketListener) Addr() net.Addr {
  function NewWebsocketListener (line 27) | func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {

FILE: pkg/util/system/system.go
  function EnableCompatibilityMode (line 21) | func EnableCompatibilityMode() {

FILE: pkg/util/system/system_android.go
  function EnableCompatibilityMode (line 25) | func EnableCompatibilityMode() {
  function fixTimezone (line 31) | func fixTimezone() {
  function fixDNSResolver (line 46) | func fixDNSResolver() {

FILE: pkg/util/tcpmux/httpconnect.go
  type HTTPConnectTCPMuxer (line 31) | type HTTPConnectTCPMuxer struct
    method readHTTPConnectRequest (line 49) | func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader)...
    method sendConnectResponse (line 70) | func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, _ ma...
    method auth (line 81) | func (muxer *HTTPConnectTCPMuxer) auth(c net.Conn, username, password ...
    method getHostFromHTTPConnect (line 105) | func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (...
  function NewHTTPConnectTCPMuxer (line 39) | func NewHTTPConnectTCPMuxer(listener net.Listener, passthrough bool, tim...
  function vhostFailed (line 96) | func vhostFailed(c net.Conn) {

FILE: pkg/util/util/types.go
  function EmptyOr (line 17) | func EmptyOr[T comparable](v T, fallback T) T {

FILE: pkg/util/util/util.go
  function RandID (line 31) | func RandID() (id string, err error) {
  function RandIDWithLen (line 36) | func RandIDWithLen(idLen int) (id string, err error) {
  function GetAuthKey (line 50) | func GetAuthKey(token string, timestamp int64) (key string) {
  function CanonicalAddr (line 58) | func CanonicalAddr(host string, port int) (addr string) {
  function ParseRangeNumbers (line 67) | func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
  function GenerateResponseErrorString (line 113) | func GenerateResponseErrorString(summary string, err error, detailed boo...
  function RandomSleep (line 120) | func RandomSleep(duration time.Duration, minRatio, maxRatio float64) tim...
  function ConstantTimeEqString (line 134) | func ConstantTimeEqString(a, b string) bool {
  function ClonePtr (line 139) | func ClonePtr[T any](v *T) *T {

FILE: pkg/util/util/util_test.go
  function TestRandId (line 9) | func TestRandId(t *testing.T) {
  function TestGetAuthKey (line 17) | func TestGetAuthKey(t *testing.T) {
  function TestParseRangeNumbers (line 23) | func TestParseRangeNumbers(t *testing.T) {
  function TestClonePtr (line 45) | func TestClonePtr(t *testing.T) {

FILE: pkg/util/version/version.go
  function Full (line 19) | func Full() string {

FILE: pkg/util/vhost/http.go
  type HTTPReverseProxyOptions (line 41) | type HTTPReverseProxyOptions struct
  type HTTPReverseProxy (line 45) | type HTTPReverseProxy struct
    method Register (line 149) | func (rp *HTTPReverseProxy) Register(routeCfg RouteConfig) error {
    method UnRegister (line 158) | func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) {
    method GetRouteConfig (line 162) | func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHT...
    method CreateConnection (line 172) | func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRout...
    method CheckAuth (line 190) | func (rp *HTTPReverseProxy) CheckAuth(domain, location, routeByHTTPUse...
    method getVhost (line 203) | func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser...
    method connectHandler (line 246) | func (rp *HTTPReverseProxy) connectHandler(rw http.ResponseWriter, req...
    method injectRequestInfoToCtx (line 269) | func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) ...
    method ServeHTTP (line 299) | func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *htt...
  function NewHTTPReverseProxy (line 52) | func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Ro...

FILE: pkg/util/vhost/https.go
  type HTTPSMuxer (line 26) | type HTTPSMuxer struct
  function NewHTTPSMuxer (line 30) | func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPS...
  function GetHTTPSHostname (line 39) | func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err ...
  function readClientHello (line 53) | func readClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) {
  function vhostFailed (line 73) | func vhostFailed(c net.Conn) {
  type readOnlyConn (line 79) | type readOnlyConn struct
    method Read (line 83) | func (conn readOnlyConn) Read(p []byte) (int, error)         { return ...
    method Write (line 84) | func (conn readOnlyConn) Write(_ []byte) (int, error)        { return ...
    method Close (line 85) | func (conn readOnlyConn) Close() error                       { return ...
    method LocalAddr (line 86) | func (conn readOnlyConn) LocalAddr() net.Addr                { return ...
    method RemoteAddr (line 87) | func (conn readOnlyConn) RemoteAddr() net.Addr               { return ...
    method SetDeadline (line 88) | func (conn readOnlyConn) SetDeadline(_ time.Time) error      { return ...
    method SetReadDeadline (line 89) | func (conn readOnlyConn) SetReadDeadline(_ time.Time) error  { return ...
    method SetWriteDeadline (line 90) | func (conn readOnlyConn) SetWriteDeadline(_ time.Time) error { return ...

FILE: pkg/util/vhost/https_test.go
  function TestGetHTTPSHostname (line 12) | func TestGetHTTPSHostname(t *testing.T) {

FILE: pkg/util/vhost/resource.go
  constant NotFound (line 30) | NotFound = `<!DOCTYPE html>
  function getNotFoundPageContent (line 53) | func getNotFoundPageContent() []byte {
  function NotFoundResponse (line 70) | func NotFoundResponse() *http.Response {

FILE: pkg/util/vhost/router.go
  type routerByHTTPUser (line 13) | type routerByHTTPUser
  type Routers (line 15) | type Routers struct
    method Add (line 36) | func (r *Routers) Add(domain, location, httpUser string, payload any) ...
    method Del (line 72) | func (r *Routers) Del(domain, location, httpUser string) {
    method Get (line 96) | func (r *Routers) Get(host, path, httpUser string) (vr *Router, exist ...
    method exist (line 120) | func (r *Routers) exist(host, path, httpUser string) (route *Router, e...
  type Router (line 21) | type Router struct
  function NewRouters (line 30) | func NewRouters() *Routers {

FILE: pkg/util/vhost/vhost.go
  type RouteInfo (line 29) | type RouteInfo
  constant RouteInfoKey (line 32) | RouteInfoKey   RouteInfo = "routeInfo"
  constant RouteConfigKey (line 33) | RouteConfigKey RouteInfo = "routeConfig"
  type RequestRouteInfo (line 36) | type RequestRouteInfo struct
  type muxFunc (line 46) | type muxFunc
  type authFunc (line 47) | type authFunc
  type hostRewriteFunc (line 48) | type hostRewriteFunc
  type successHookFunc (line 49) | type successHookFunc
  type failHookFunc (line 50) | type failHookFunc
  type Muxer (line 56) | type Muxer struct
    method SetCheckAuthFunc (line 83) | func (v *Muxer) SetCheckAuthFunc(f authFunc) *Muxer {
    method SetSuccessHookFunc (line 88) | func (v *Muxer) SetSuccessHookFunc(f successHookFunc) *Muxer {
    method SetFailHookFunc (line 93) | func (v *Muxer) SetFailHookFunc(f failHookFunc) *Muxer {
    method SetRewriteHostFunc (line 98) | func (v *Muxer) SetRewriteHostFunc(f hostRewriteFunc) *Muxer {
    method Close (line 103) | func (v *Muxer) Close() error {
    method Listen (line 131) | func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *List...
    method getListener (line 150) | func (v *Muxer) getListener(name, path, httpUser string) (*Listener, b...
    method run (line 190) | func (v *Muxer) run() {
    method handle (line 200) | func (v *Muxer) handle(c net.Conn) {
  function NewMuxer (line 68) | func NewMuxer(
  type ChooseEndpointFunc (line 107) | type ChooseEndpointFunc
  type CreateConnFunc (line 109) | type CreateConnFunc
  type CreateConnByEndpointFunc (line 111) | type CreateConnByEndpointFunc
  type RouteConfig (line 114) | type RouteConfig struct
  type Listener (line 258) | type Listener struct
    method Accept (line 270) | func (l *Listener) Accept() (net.Conn, error) {
    method Close (line 292) | func (l *Listener) Close() error {
    method Name (line 298) | func (l *Listener) Name() string {
    method Addr (line 302) | func (l *Listener) Addr() net.Addr {

FILE: pkg/util/wait/backoff.go
  type BackoffFunc (line 24) | type BackoffFunc
    method Backoff (line 26) | func (f BackoffFunc) Backoff(previousDuration time.Duration, previousC...
  type BackoffManager (line 30) | type BackoffManager interface
  type FastBackoffOptions (line 34) | type FastBackoffOptions struct
  type fastBackoffImpl (line 49) | type fastBackoffImpl struct
    method Backoff (line 66) | func (f *fastBackoffImpl) Backoff(previousDuration time.Duration, prev...
  function NewFastBackoffManager (line 59) | func NewFastBackoffManager(options FastBackoffOptions) BackoffManager {
  function BackoffUntil (line 115) | func BackoffUntil(f func() (bool, error), backoff BackoffManager, slidin...
  function Jitter (line 159) | func Jitter(duration time.Duration, maxFactor float64) time.Duration {
  function Until (line 167) | func Until(f func(), period time.Duration, stopCh <-chan struct{}) {

FILE: pkg/util/xlog/ctx.go
  type key (line 21) | type key
  constant xlogKey (line 24) | xlogKey key = 0
  function NewContext (line 27) | func NewContext(ctx context.Context, xl *Logger) context.Context {
  function FromContext (line 31) | func FromContext(ctx context.Context) (xl *Logger, ok bool) {
  function FromContextSafe (line 36) | func FromContextSafe(ctx context.Context) *Logger {

FILE: pkg/util/xlog/log_writer.go
  type LogWriter (line 21) | type LogWriter struct
    method Write (line 26) | func (w LogWriter) Write(p []byte) (n int, err error) {
  function NewTraceWriter (line 32) | func NewTraceWriter(xl *Logger) LogWriter {
  function NewDebugWriter (line 39) | func NewDebugWriter(xl *Logger) LogWriter {
  function NewInfoWriter (line 46) | func NewInfoWriter(xl *Logger) LogWriter {
  function NewWarnWriter (line 53) | func NewWarnWriter(xl *Logger) LogWriter {
  function NewErrorWriter (line 60) | func NewErrorWriter(xl *Logger) LogWriter {

FILE: pkg/util/xlog/xlog.go
  type LogPrefix (line 24) | type LogPrefix struct
  type Logger (line 34) | type Logger struct
    method ResetPrefixes (line 46) | func (l *Logger) ResetPrefixes() (old []LogPrefix) {
    method AppendPrefix (line 53) | func (l *Logger) AppendPrefix(prefix string) *Logger {
    method AddPrefix (line 61) | func (l *Logger) AddPrefix(prefix LogPrefix) *Logger {
    method renderPrefixString (line 81) | func (l *Logger) renderPrefixString() {
    method Spawn (line 91) | func (l *Logger) Spawn() *Logger {
    method Errorf (line 98) | func (l *Logger) Errorf(format string, v ...any) {
    method Warnf (line 102) | func (l *Logger) Warnf(format string, v ...any) {
    method Infof (line 106) | func (l *Logger) Infof(format string, v ...any) {
    method Debugf (line 110) | func (l *Logger) Debugf(format string, v ...any) {
    method Tracef (line 114) | func (l *Logger) Tracef(format string, v ...any) {
  function New (line 40) | func New() *Logger {

FILE: pkg/virtual/client.go
  type ClientOptions (line 28) | type ClientOptions struct
  type Client (line 34) | type Client struct
    method PeerListener (line 71) | func (c *Client) PeerListener() net.Listener {
    method UpdateProxyConfigurer (line 75) | func (c *Client) UpdateProxyConfigurer(proxyCfgs []v1.ProxyConfigurer) {
    method Run (line 79) | func (c *Client) Run(ctx context.Context) error {
    method Service (line 83) | func (c *Client) Service() *client.Service {
    method Close (line 87) | func (c *Client) Close() {
  function NewClient (line 39) | func NewClient(options ClientOptions) (*Client, error) {
  type pipeConnector (line 92) | type pipeConnector struct
    method Open (line 96) | func (pc *pipeConnector) Open() error {
    method Connect (line 100) | func (pc *pipeConnector) Connect() (net.Conn, error) {
    method Close (line 110) | func (pc *pipeConnector) Close() error {

FILE: pkg/vnet/controller.go
  constant maxPacketSize (line 36) | maxPacketSize = 1420
  type Controller (line 39) | type Controller struct
    method Init (line 55) | func (c *Controller) Init() error {
    method Run (line 64) | func (c *Controller) Run() error {
    method handlePacket (line 82) | func (c *Controller) handlePacket(buf []byte) {
    method Stop (line 133) | func (c *Controller) Stop() error {
    method readLoopClient (line 141) | func (c *Controller) readLoopClient(ctx context.Context, conn io.ReadW...
    method readLoopServer (line 193) | func (c *Controller) readLoopServer(ctx context.Context, conn io.ReadW...
    method RegisterClientRoute (line 244) | func (c *Controller) RegisterClientRoute(ctx context.Context, name str...
    method UnregisterClientRoute (line 250) | func (c *Controller) UnregisterClientRoute(name string) {
    method StartServerConnReadLoop (line 256) | func (c *Controller) StartServerConnReadLoop(ctx context.Context, conn...
  function NewController (line 47) | func NewController(cfg v1.VirtualNetConfig) *Controller {
  function ParseRoutes (line 261) | func ParseRoutes(routeStrings []string) ([]net.IPNet, error) {
  type clientRouter (line 274) | type clientRouter struct
    method addRoute (line 285) | func (r *clientRouter) addRoute(name string, routes []net.IPNet, conn ...
    method findConn (line 295) | func (r *clientRouter) findConn(dst net.IP) (io.Writer, error) {
    method delRoute (line 308) | func (r *clientRouter) delRoute(name string) {
    method removeConnRoute (line 314) | func (r *clientRouter) removeConnRoute(conn io.Writer) {
  function newClientRouter (line 279) | func newClientRouter() *clientRouter {
  type serverRouter (line 326) | type serverRouter struct
    method findConnBySrc (line 337) | func (r *serverRouter) findConnBySrc(src net.IP) (io.Writer, error) {
    method registerSrcIP (line 347) | func (r *serverRouter) registerSrcIP(src net.IP, conn io.Writer) {
    method cleanupConnIPs (line 373) | func (r *serverRouter) cleanupConnIPs(conn io.Writer) {
  function newServerRouter (line 331) | func newServerRouter() *serverRouter {
  type routeElement (line 385) | type routeElement struct

FILE: pkg/vnet/message.go
  constant maxMessageSize (line 25) | maxMessageSize = 1024 * 1024
  function ReadMessage (line 31) | func ReadMessage(r io.Reader) ([]byte, error) {
  function WriteMessage (line 58) | func WriteMessage(w io.Writer, data []byte) error {

FILE: pkg/vnet/tun.go
  constant offset (line 26) | offset            = 16
  constant defaultPacketSize (line 27) | defaultPacketSize = 1420
  type TunDevice (line 30) | type TunDevice interface
  function OpenTun (line 34) | func OpenTun(ctx context.Context, addr string) (TunDevice, error) {
  type tunDeviceWrapper (line 62) | type tunDeviceWrapper struct
    method Read (line 70) | func (d *tunDeviceWrapper) Read(p []byte) (int, error) {
    method Write (line 98) | func (d *tunDeviceWrapper) Write(p []byte) (int, error) {
    method Close (line 107) | func (d *tunDeviceWrapper) Close() error {

FILE: pkg/vnet/tun_darwin.go
  constant defaultTunName (line 27) | defaultTunName = "utun"
  constant defaultMTU (line 28) | defaultMTU     = 1420
  function openTun (line 31) | func openTun(_ context.Context, addr string) (tun.Device, error) {
  function generatePeerIP (line 65) | func generatePeerIP(ip net.IP) net.IP {
  function addRoutes (line 77) | func addRoutes(ifName string, routes []net.IPNet) error {

FILE: pkg/vnet/tun_linux.go
  constant baseTunName (line 31) | baseTunName = "utun"
  constant defaultMTU (line 32) | defaultMTU  = 1420
  function openTun (line 35) | func openTun(_ context.Context, addr string) (tun.Device, error) {
  function findNextTunName (line 84) | func findNextTunName(basename string) (string, error) {
  function addRoutes (line 111) | func addRoutes(ifn *net.Interface, cidr *net.IPNet) error {
  function getFallbackTunName (line 124) | func getFallbackTunName(baseName, addr string) string {

FILE: pkg/vnet/tun_unsupported.go
  function openTun (line 27) | func openTun(_ context.Context, _ string) (tun.Device, error) {

FILE: server/api_router.go
  method registerRouteHandlers (line 27) | func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegister...
  function healthz (line 62) | func healthz(w http.ResponseWriter, _ *http.Request) {

FILE: server/control.go
  type ControlManager (line 46) | type ControlManager struct
    method Add (line 59) | func (cm *ControlManager) Add(runID string, ctl *Control) (old *Contro...
    method Del (line 73) | func (cm *ControlManager) Del(runID string, ctl *Control) {
    method GetByID (line 81) | func (cm *ControlManager) GetByID(runID string) (ctl *Control, ok bool) {
    method Close (line 88) | func (cm *ControlManager) Close() error {
  function NewControlManager (line 53) | func NewControlManager() *ControlManager {
  type SessionContext (line 99) | type SessionContext struct
  type Control (line 122) | type Control struct
    method Start (line 190) | func (ctl *Control) Start() {
    method Close (line 207) | func (ctl *Control) Close() error {
    method Replaced (line 212) | func (ctl *Control) Replaced(newCtl *Control) {
    method RegisterWorkConn (line 219) | func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
    method GetWorkConn (line 242) | func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
    method heartbeatWorker (line 286) | func (ctl *Control) heartbeatWorker() {
    method WaitClosed (line 302) | func (ctl *Control) WaitClosed() {
    method loginUserInfo (line 306) | func (ctl *Control) loginUserInfo() plugin.UserInfo {
    method closeProxy (line 314) | func (ctl *Control) closeProxy(pxy proxy.Proxy) {
    method worker (line 330) | func (ctl *Control) worker() {
    method registerMsgHandlers (line 358) | func (ctl *Control) registerMsgHandlers() {
    method handleNewProxy (line 367) | func (ctl *Control) handleNewProxy(m msg.Message) {
    method handlePing (line 402) | func (ctl *Control) handlePing(m msg.Message) {
    method handleNatHoleVisitor (line 427) | func (ctl *Control) handleNatHoleVisitor(m msg.Message) {
    method handleNatHoleClient (line 432) | func (ctl *Control) handleNatHoleClient(m msg.Message) {
    method handleNatHoleReport (line 437) | func (ctl *Control) handleNatHoleReport(m msg.Message) {
    method handleCloseProxy (line 442) | func (ctl *Control) handleCloseProxy(m msg.Message) {
    method RegisterProxy (line 449) | func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr st...
    method CloseProxy (line 526) | func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
  function NewControl (line 160) | func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Contr...

FILE: server/controller/resource.go
  type ResourceController (line 28) | type ResourceController struct
    method Close (line 66) | func (rc *ResourceController) Close() error {

FILE: server/group/base.go
  type baseGroup (line 13) | type baseGroup struct
    method initBase (line 26) | func (bg *baseGroup) initBase(group, groupKey string, realLn net.Liste...
    method worker (line 37) | func (bg *baseGroup) worker(realLn net.Listener, acceptCh chan<- net.C...
    method newListener (line 55) | func (bg *baseGroup) newListener(addr net.Addr) *Listener {
    method closeListener (line 63) | func (bg *baseGroup) closeListener(ln *Listener) {

FILE: server/group/base_test.go
  type fakeLn (line 14) | type fakeLn struct
    method Accept (line 27) | func (f *fakeLn) Accept() (net.Conn, error) {
    method Close (line 36) | func (f *fakeLn) Close() error {
    method Addr (line 41) | func (f *fakeLn) Addr() net.Addr { return fakeAddr("127.0.0.1:9999") }
    method inject (line 43) | func (f *fakeLn) inject(c net.Conn) {
  function newFakeLn (line 20) | func newFakeLn() *fakeLn {
  function TestBaseGroup_WorkerFanOut (line 50) | func TestBaseGroup_WorkerFanOut(t *testing.T) {
  function TestBaseGroup_WorkerStopsOnListenerClose (line 72) | func TestBaseGroup_WorkerStopsOnListenerClose(t *testing.T) {
  function TestBaseGroup_WorkerClosesConnOnClosedChannel (line 91) | func TestBaseGroup_WorkerClosesConnOnClosedChannel(t *testing.T) {
  function TestBaseGroup_CloseLastListenerTriggersCleanup (line 121) | func TestBaseGroup_CloseLastListenerTriggersCleanup(t *testing.T) {
  function TestBaseGroup_CloseOneOfTwoListeners (line 141) | func TestBaseGroup_CloseOneOfTwoListeners(t *testing.T) {

FILE: server/group/http.go
  type HTTPGroupController (line 14) | type HTTPGroupController struct
    method Register (line 26) | func (ctl *HTTPGroupController) Register(
    method UnRegister (line 42) | func (ctl *HTTPGroupController) UnRegister(proxyName, group string, _ ...
  function NewHTTPGroupController (line 19) | func NewHTTPGroupController(vhostRouter *vhost.Routers) *HTTPGroupContro...
  type HTTPGroup (line 50) | type HTTPGroup struct
    method Register (line 73) | func (g *HTTPGroup) Register(
    method UnRegister (line 118) | func (g *HTTPGroup) UnRegister(proxyName string) {
    method createConn (line 137) | func (g *HTTPGroup) createConn(remoteAddr string) (net.Conn, error) {
    method chooseEndpoint (line 160) | func (g *HTTPGroup) chooseEndpoint() (string, error) {
    method createConnByEndpoint (line 181) | func (g *HTTPGroup) createConnByEndpoint(endpoint, remoteAddr string) ...
  function NewHTTPGroup (line 65) | func NewHTTPGroup(ctl *HTTPGroupController) *HTTPGroup {

FILE: server/group/https.go
  type HTTPSGroupController (line 24) | type HTTPSGroupController struct
    method Listen (line 36) | func (ctl *HTTPSGroupController) Listen(
  function NewHTTPSGroupController (line 29) | func NewHTTPSGroupController(httpsMuxer *vhost.HTTPSMuxer) *HTTPSGroupCo...
  type HTTPSGroup (line 53) | type HTTPSGroup struct
    method Listen (line 66) | func (g *HTTPSGroup) Listen(
  function NewHTTPSGroup (line 60) | func NewHTTPSGroup(ctl *HTTPSGroupController) *HTTPSGroup {

FILE: server/group/listener.go
  type Listener (line 10) | type Listener struct
    method Accept (line 27) | func (ln *Listener) Accept() (net.Conn, error) {
    method Addr (line 39) | func (ln *Listener) Addr() net.Addr {
    method Close (line 43) | func (ln *Listener) Close() error {
  function newListener (line 18) | func newListener(acceptCh <-chan net.Conn, addr net.Addr, onClose func(*...

FILE: server/group/listener_test.go
  function TestListener_Accept (line 11) | func TestListener_Accept(t *testing.T) {
  function TestListener_AcceptAfterChannelClose (line 25) | func TestListener_AcceptAfterChannelClose(t *testing.T) {
  function TestListener_AcceptAfterListenerClose (line 34) | func TestListener_AcceptAfterListenerClose(t *testing.T) {
  function TestListener_DoubleClose (line 43) | func TestListener_DoubleClose(t *testing.T) {
  function TestListener_Addr (line 58) | func TestListener_Addr(t *testing.T) {
  type fakeAddr (line 65) | type fakeAddr
    method Network (line 67) | func (a fakeAddr) Network() string { return "tcp" }
    method String (line 68) | func (a fakeAddr) String() string  { return string(a) }

FILE: server/group/registry.go
  type groupRegistry (line 9) | type groupRegistry struct
  function newGroupRegistry (line 14) | func newGroupRegistry[G any]() groupRegistry[G] {
  method getOrCreate (line 20) | func (r *groupRegistry[G]) getOrCreate(key string, newFn func() G) G {
  method get (line 31) | func (r *groupRegistry[G]) get(key string) (G, bool) {
  method isCurrent (line 40) | func (r *groupRegistry[G]) isCurrent(key string, matchFn func(G) bool) b...
  method removeIf (line 49) | func (r *groupRegistry[G]) removeIf(key string, fn func(G) bool) {

FILE: server/group/registry_test.go
  function TestGetOrCreate_New (line 11) | func TestGetOrCreate_New(t *testing.T) {
  function TestGetOrCreate_Existing (line 20) | func TestGetOrCreate_Existing(t *testing.T) {
  function TestGet_ExistingAndMissing (line 31) | func TestGet_ExistingAndMissing(t *testing.T) {
  function TestIsCurrent (line 44) | func TestIsCurrent(t *testing.T) {
  function TestRemoveIf (line 55) | func TestRemoveIf(t *testing.T) {
  function TestConcurrentGetOrCreateAndRemoveIf (line 80) | func TestConcurrentGetOrCreateAndRemoveIf(t *testing.T) {

FILE: server/group/tcp.go
  type TCPGroupCtl (line 25) | type TCPGroupCtl struct
    method Listen (line 40) | func (tgc *TCPGroupCtl) Listen(proxyName string, group string, groupKe...
  function NewTCPGroupCtl (line 31) | func NewTCPGroupCtl(portManager *ports.Manager) *TCPGroupCtl {
  type TCPGroup (line 56) | type TCPGroup struct
    method Listen (line 75) | func (tg *TCPGroup) Listen(proxyName string, group string, groupKey st...
  function NewTCPGroup (line 66) | func NewTCPGroup(ctl *TCPGroupCtl) *TCPGroup {

FILE: server/group/tcpmux.go
  type TCPMuxGroupCtl (line 28) | type TCPMuxGroupCtl struct
    method Listen (line 43) | func (tmgc *TCPMuxGroupCtl) Listen(
  function NewTCPMuxGroupCtl (line 34) | func NewTCPMuxGroupCtl(tcpMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxe...
  type TCPMuxGroup (line 67) | type TCPMuxGroup struct
    method HTTPConnectListen (line 87) | func (tmg *TCPMuxGroup) HTTPConnectListen(
  function NewTCPMuxGroup (line 78) | func NewTCPMuxGroup(ctl *TCPMuxGroupCtl) *TCPMuxGroup {

FILE: server/http/controller.go
  type Controller (line 36) | type Controller struct
    method APIServerInfo (line 60) | func (c *Controller) APIServerInfo(ctx *httppkg.Context) (any, error) {
    method APIClientList (line 88) | func (c *Controller) APIClientList(ctx *httppkg.Context) (any, error) {
    method APIClientDetail (line 130) | func (c *Controller) APIClientDetail(ctx *httppkg.Context) (any, error) {
    method APIProxyByType (line 149) | func (c *Controller) APIProxyByType(ctx *httppkg.Context) (any, error) {
    method APIProxyByTypeAndName (line 162) | func (c *Controller) APIProxyByTypeAndName(ctx *httppkg.Context) (any,...
    method APIProxyTraffic (line 175) | func (c *Controller) APIProxyTraffic(ctx *httppkg.Context) (any, error) {
    method APIProxyByName (line 192) | func (c *Controller) APIProxyByName(ctx *httppkg.Context) (any, error) {
    method DeleteProxies (line 222) | func (c *Controller) DeleteProxies(ctx *httppkg.Context) (any, error) {
    method getProxyStatsByType (line 232) | func (c *Controller) getProxyStatsByType(proxyType string) (proxyInfos...
    method getProxyStatsByTypeAndName (line 257) | func (c *Controller) getProxyStatsByTypeAndName(proxyType string, prox...
  type ProxyManager (line 43) | type ProxyManager interface
  function NewController (line 47) | func NewController(
  function buildClientInfoResp (line 283) | func buildClientInfoResp(info registry.ClientInfo) model.ClientInfoResp {
  function toUnix (line 302) | func toUnix(t time.Time) int64 {
  function matchStatusFilter (line 309) | func matchStatusFilter(online bool, filter string) bool {
  function getConfFromConfigurer (line 322) | func getConfFromConfigurer(cfg v1.ProxyConfigurer) any {

FILE: server/http/controller_test.go
  function TestGetConfFromConfigurerKeepsPluginFields (line 24) | func TestGetConfFromConfigurerKeepsPluginFields(t *testing.T) {

FILE: server/http/model/types.go
  type ServerInfoResp (line 21) | type ServerInfoResp struct
  type ClientInfoResp (line 43) | type ClientInfoResp struct
  type BaseOutConf (line 57) | type BaseOutConf struct
  type TCPOutConf (line 61) | type TCPOutConf struct
  type TCPMuxOutConf (line 66) | type TCPMuxOutConf struct
  type UDPOutConf (line 73) | type UDPOutConf struct
  type HTTPOutConf (line 78) | type HTTPOutConf struct
  type HTTPSOutConf (line 85) | type HTTPSOutConf struct
  type STCPOutConf (line 90) | type STCPOutConf struct
  type XTCPOutConf (line 94) | type XTCPOutConf struct
  type ProxyStatsInfo (line 99) | type ProxyStatsInfo struct
  type GetProxyInfoResp (line 112) | type GetProxyInfoResp struct
  type GetProxyStatsResp (line 117) | type GetProxyStatsResp struct
  type GetProxyTrafficResp (line 131) | type GetProxyTrafficResp struct

FILE: server/metrics/metrics.go
  type ServerMetrics (line 7) | type ServerMetrics interface
  function Register (line 22) | func Register(m ServerMetrics) {
  type noopServerMetrics (line 28) | type noopServerMetrics struct
    method NewClient (line 30) | func (noopServerMetrics) NewClient()                              {}
    method CloseClient (line 31) | func (noopServerMetrics) CloseClient()                            {}
    method NewProxy (line 32) | func (noopServerMetrics) NewProxy(string, string, string, string) {}
    method CloseProxy (line 33) | func (noopServerMetrics) CloseProxy(string, string)               {}
    method OpenConnection (line 34) | func (noopServerMetrics) OpenConnection(string, string)           {}
    method CloseConnection (line 35) | func (noopServerMetrics) CloseConnection(string, string)          {}
    method AddTrafficIn (line 36) | func (noopServerMetrics) AddTrafficIn(string, string, int64)      {}
    method AddTrafficOut (line 37) | func (noopServerMetrics) AddTrafficOut(string, string, int64)     {}

FILE: server/ports/ports.go
  constant MinPort (line 14) | MinPort                    = 1
  constant MaxPort (line 15) | MaxPort                    = 65535
  constant MaxPortReservedDuration (line 16) | MaxPortReservedDuration    = time.Duration(24) * time.Hour
  constant CleanReservedPortsInterval (line 17) | CleanReservedPortsInterval = time.Hour
  type PortCtx (line 27) | type PortCtx struct
  type Manager (line 34) | type Manager struct
    method Acquire (line 71) | func (pm *Manager) Acquire(name string, port int) (realPort int, err e...
    method isPortAvailable (line 143) | func (pm *Manager) isPortAvailable(port int) bool {
    method Release (line 165) | func (pm *Manager) Release(port int) {
    method cleanReservedPortsWorker (line 177) | func (pm *Manager) cleanReservedPortsWorker() {
  function NewManager (line 44) | func NewManager(netType string, bindAddr string, allowPorts []types.Port...

FILE: server/proxy/http.go
  function init (line 33) | func init() {
  type HTTPProxy (line 37) | type HTTPProxy struct
    method Run (line 55) | func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
    method GetRealConn (line 114) | func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Con...
    method updateStatsAfterClosedConn (line 153) | func (pxy *HTTPProxy) updateStatsAfterClosedConn(totalRead, totalWrite...
    method Close (line 161) | func (pxy *HTTPProxy) Close() {
  function NewHTTPProxy (line 44) | func NewHTTPProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/https.go
  function init (line 27) | func init() {
  type HTTPSProxy (line 31) | type HTTPSProxy struct
    method Run (line 47) | func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
    method Close (line 74) | func (pxy *HTTPSProxy) Close() {
    method listenForDomain (line 78) | func (pxy *HTTPSProxy) listenForDomain(routeConfig *vhost.RouteConfig,...
  function NewHTTPSProxy (line 36) | func NewHTTPSProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/proxy.go
  function RegisterProxyFactory (line 43) | func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*Base...
  type GetWorkConnFn (line 47) | type GetWorkConnFn
  type Proxy (line 49) | type Proxy interface
  type BaseProxy (line 63) | type BaseProxy struct
    method GetName (line 82) | func (pxy *BaseProxy) GetName() string {
    method Context (line 86) | func (pxy *BaseProxy) Context() context.Context {
    method GetUsedPortsNum (line 90) | func (pxy *BaseProxy) GetUsedPortsNum() int {
    method GetResourceController (line 94) | func (pxy *BaseProxy) GetResourceController() *controller.ResourceCont...
    method GetUserInfo (line 98) | func (pxy *BaseProxy) GetUserInfo() plugin.UserInfo {
    method GetLoginMsg (line 102) | func (pxy *BaseProxy) GetLoginMsg() *msg.Login {
    method GetLimiter (line 106) | func (pxy *BaseProxy) GetLimiter() *rate.Limiter {
    method GetConfigurer (line 110) | func (pxy *BaseProxy) GetConfigurer() v1.ProxyConfigurer {
    method Close (line 114) | func (pxy *BaseProxy) Close() {
    method GetWorkConnFromPool (line 124) | func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn...
    method startVisitorListener (line 178) | func (pxy *BaseProxy) startVisitorListener(secretKey string, allowUser...
    method buildDomains (line 194) | func (pxy *BaseProxy) buildDomains(customDomains []string, subDomain s...
    method startCommonTCPListenersHandler (line 208) | func (pxy *BaseProxy) startCommonTCPListenersHandler() {
    method handleUserTCPConnection (line 244) | func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
  type Options (line 305) | type Options struct
  function NewProxy (line 316) | func NewProxy(ctx context.Context, options *Options) (pxy Proxy, err err...
  type Manager (line 353) | type Manager struct
    method Add (line 366) | func (pm *Manager) Add(name string, pxy Proxy) error {
    method Exist (line 377) | func (pm *Manager) Exist(name string) bool {
    method Del (line 384) | func (pm *Manager) Del(name string) {
    method GetByName (line 390) | func (pm *Manager) GetByName(name string) (pxy Proxy, ok bool) {
  function NewManager (line 360) | func NewManager() *Manager {

FILE: server/proxy/stcp.go
  function init (line 23) | func init() {
  type STCPProxy (line 27) | type STCPProxy struct
    method Run (line 43) | func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
    method Close (line 48) | func (pxy *STCPProxy) Close() {
  function NewSTCPProxy (line 32) | func NewSTCPProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/sudp.go
  function init (line 23) | func init() {
  type SUDPProxy (line 27) | type SUDPProxy struct
    method Run (line 43) | func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
    method Close (line 48) | func (pxy *SUDPProxy) Close() {
  function NewSUDPProxy (line 32) | func NewSUDPProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/tcp.go
  function init (line 26) | func init() {
  type TCPProxy (line 30) | type TCPProxy struct
    method Run (line 49) | func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
    method Close (line 91) | func (pxy *TCPProxy) Close() {
  function NewTCPProxy (line 37) | func NewTCPProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/tcpmux.go
  function init (line 28) | func init() {
  type TCPMuxProxy (line 32) | type TCPMuxProxy struct
    method httpConnectListen (line 48) | func (pxy *TCPMuxProxy) httpConnectListen(
    method httpConnectRun (line 74) | func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
    method Run (line 90) | func (pxy *TCPMuxProxy) Run() (remoteAddr string, err error) {
    method Close (line 104) | func (pxy *TCPMuxProxy) Close() {
  function NewTCPMuxProxy (line 37) | func NewTCPMuxProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/udp.go
  function init (line 37) | func init() {
  type UDPProxy (line 41) | type UDPProxy struct
    method Run (line 78) | func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
    method Close (line 248) | func (pxy *UDPProxy) Close() {
  function NewUDPProxy (line 66) | func NewUDPProxy(baseProxy *BaseProxy) Proxy {

FILE: server/proxy/xtcp.go
  function init (line 26) | func init() {
  type XTCPProxy (line 30) | type XTCPProxy struct
    method Run (line 50) | func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
    method Close (line 90) | func (pxy *XTCPProxy) Close() {
  function NewXTCPProxy (line 38) | func NewXTCPProxy(baseProxy *BaseProxy) Proxy {

FILE: server/registry/registry.go
  type ClientInfo (line 24) | type ClientInfo struct
    method ClientID (line 149) | func (info ClientInfo) ClientID() string {
  type ClientRegistry (line 40) | type ClientRegistry struct
    method Register (line 54) | func (cr *ClientRegistry) Register(user, rawClientID, runID, hostname,...
    method MarkOfflineByRunID (line 103) | func (cr *ClientRegistry) MarkOfflineByRunID(runID string) {
    method List (line 125) | func (cr *ClientRegistry) List() []ClientInfo {
    method GetByKey (line 137) | func (cr *ClientRegistry) GetByKey(key string) (ClientInfo, bool) {
    method composeClientKey (line 156) | func (cr *ClientRegistry) composeClientKey(user, id string) string {
  function NewClientRegistry (line 46) | func NewClientRegistry() *ClientRegistry {

FILE: server/service.go
  constant connReadTimeout (line 60) | connReadTimeout       time.Duration = 10 * time.Second
  constant vhostReadWriteTimeout (line 61) | vhostReadWriteTimeout time.Duration = 30 * time.Second
  function init (line 64) | func init() {
  type Service (line 75) | type Service struct
    method Run (line 355) | func (svr *Service) Run(ctx context.Context) {
    method Close (line 398) | func (svr *Service) Close() error {
    method handleConnection (line 432) | func (svr *Service) handleConnection(ctx context.Context, conn net.Con...
    method HandleListener (line 498) | func (svr *Service) HandleListener(l net.Listener, internal bool) {
    method HandleQUICListener (line 557) | func (svr *Service) HandleQUICListener(l *quic.Listener) {
    method RegisterControl (line 580) | func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Lo...
    method RegisterWorkConn (line 654) | func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.Ne...
    method RegisterVisitorConn (line 686) | func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *...
  function NewService (line 133) | func NewService(cfg *v1.ServerConfig) (*Service, error) {

FILE: server/visitor/visitor.go
  type listenerBundle (line 30) | type listenerBundle struct
  type Manager (line 37) | type Manager struct
    method Listen (line 49) | func (vm *Manager) Listen(name string, sk string, allowUsers []string)...
    method NewConn (line 66) | func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64...
    method CloseListener (line 101) | func (vm *Manager) CloseListener(name string) {
  function NewManager (line 43) | func NewManager() *Manager {

FILE: test/e2e/e2e.go
  function RunE2ETests (line 32) | func RunE2ETests(t *testing.T) {
  function setupSuite (line 55) | func setupSuite() {
  function setupSuitePerGinkgoNode (line 64) | func setupSuitePerGinkgoNode() {

FILE: test/e2e/e2e_test.go
  function handleFlags (line 23) | func handleFlags() {
  function TestMain (line 28) | func TestMain(m *testing.M) {
  function TestE2E (line 41) | func TestE2E(t *testing.T) {

FILE: test/e2e/framework/cleanup.go
  type CleanupActionHandle (line 8) | type CleanupActionHandle
  type cleanupFuncHandle (line 10) | type cleanupFuncHandle struct
  function AddCleanupAction (line 24) | func AddCleanupAction(fn func()) CleanupActionHandle {
  function RemoveCleanupAction (line 35) | func RemoveCleanupAction(p CleanupActionHandle) {
  function RunCleanupActions (line 49) | func RunCleanupActions() {

FILE: test/e2e/framework/client.go
  method APIClientForFrpc (line 7) | func (f *Framework) APIClientForFrpc(port int) *clientsdk.Client {

FILE: test/e2e/framework/consts/consts.go
  constant TestString (line 11) | TestString = "frp is a fast reverse proxy to help you expose a local ser...
  constant DefaultTimeout (line 13) | DefaultTimeout = 2 * time.Second
  function init (line 47) | func init() {

FILE: test/e2e/framework/expect.go
  function ExpectEqual (line 8) | func ExpectEqual(actual any, extra any, explain ...any) {
  function ExpectEqualValues (line 13) | func ExpectEqualValues(actual any, extra any, explain ...any) {
  function ExpectEqualValuesWithOffset (line 17) | func ExpectEqualValuesWithOffset(offset int, actual any, extra any, expl...
  function ExpectNotEqual (line 22) | func ExpectNotEqual(actual any, extra any, explain ...any) {
  function ExpectError (line 27) | func ExpectError(err error, explain ...any) {
  function ExpectErrorWithOffset (line 31) | func ExpectErrorWithOffset(offset int, err error, explain ...any) {
  function ExpectNoError (line 36) | func ExpectNoError(err error, explain ...any) {
  function ExpectNoErrorWithOffset (line 42) | func ExpectNoErrorWithOffset(offset int, err error, explain ...any) {
  function ExpectContainSubstring (line 46) | func ExpectContainSubstring(actual, substr string, explain ...any) {
  function ExpectConsistOf (line 51) | func ExpectConsistOf(actual any, extra any, explain ...any) {
  function ExpectContainElements (line 55) | func ExpectContainElements(actual any, extra any, explain ...any) {
  function ExpectNotContainElements (line 59) | func ExpectNotContainElements(actual any, extra any, explain ...any) {
  function ExpectHaveKey (line 64) | func ExpectHaveKey(actual any, key any, explain ...any) {
  function ExpectEmpty (line 69) | func ExpectEmpty(actual any, explain ...any) {
  function ExpectTrue (line 73) | func ExpectTrue(actual any, explain ...any) {
  function ExpectTrueWithOffset (line 77) | func ExpectTrueWithOffset(offset int, actual any, explain ...any) {

FILE: test/e2e/framework/framework.go
  type Options (line 19) | type Options struct
  type Framework (line 26) | type Framework struct
    method BeforeEach (line 87) | func (f *Framework) BeforeEach() {
    method AfterEach (line 111) | func (f *Framework) AfterEach() {
    method genPortsFromTemplates (line 174) | func (f *Framework) genPortsFromTemplates(templates []string) (ports m...
    method RenderTemplates (line 204) | func (f *Framework) RenderTemplates(templates []string) (outs []string...
    method PortByName (line 233) | func (f *Framework) PortByName(name string) int {
    method AllocPort (line 237) | func (f *Framework) AllocPort() int {
    method ReleasePort (line 244) | func (f *Framework) ReleasePort(port int) {
    method RunServer (line 248) | func (f *Framework) RunServer(portName string, s server.Server) {
    method SetEnvs (line 257) | func (f *Framework) SetEnvs(envs []string) {
    method WriteTempFile (line 261) | func (f *Framework) WriteTempFile(name string, content string) string {
  function NewDefaultFramework (line 64) | func NewDefaultFramework() *Framework {
  function NewFramework (line 75) | func NewFramework(opt Options) *Framework {

FILE: test/e2e/framework/log.go
  function nowStamp (line 10) | func nowStamp() string {
  function log (line 14) | func log(level string, format string, args ...any) {
  function Logf (line 19) | func Logf(format string, args ...any) {
  function Failf (line 25) | func Failf(format string, args ...any) {

FILE: test/e2e/framework/mockservers.go
  constant TCPEchoServerPort (line 16) | TCPEchoServerPort    = "TCPEchoServerPort"
  constant UDPEchoServerPort (line 17) | UDPEchoServerPort    = "UDPEchoServerPort"
  constant UDSEchoServerAddr (line 18) | UDSEchoServerAddr    = "UDSEchoServerAddr"
  constant HTTPSimpleServerPort (line 19) | HTTPSimpleServerPort = "HTTPSimpleServerPort"
  type MockServers (line 22) | type MockServers struct
    method Run (line 49) | func (m *MockServers) Run() error {
    method Close (line 62) | func (m *MockServers) Close() {
    method GetTemplateParams (line 70) | func (m *MockServers) GetTemplateParams() map[string]any {
    method GetParam (line 79) | func (m *MockServers) GetParam(key string) any {
  function NewMockServers (line 29) | func NewMockServers(portAllocator *port.Allocator) *MockServers {

FILE: test/e2e/framework/process.go
  method RunProcesses (line 19) | func (f *Framework) RunProcesses(serverTemplate string, clientTemplates ...
  method RunFrps (line 86) | func (f *Framework) RunFrps(args ...string) (*process.Process, string, e...
  method RunFrpc (line 100) | func (f *Framework) RunFrpc(args ...string) (*process.Process, string, e...
  method GenerateConfigFile (line 114) | func (f *Framework) GenerateConfigFile(content string) string {
  function waitForClientProxyReady (line 125) | func waitForClientProxyReady(configPath string, p *process.Process, time...
  function WaitForTCPUnreachable (line 148) | func WaitForTCPUnreachable(addr string, interval, timeout time.Duration)...
  function WaitForTCPReady (line 172) | func WaitForTCPReady(addr string, timeout time.Duration) error {

FILE: test/e2e/framework/request.go
  function SpecifiedHTTPBodyHandler (line 12) | func SpecifiedHTTPBodyHandler(body []byte) http.HandlerFunc {
  function ExpectResponseCode (line 18) | func ExpectResponseCode(code int) EnsureFunc {
  function NewRequest (line 29) | func NewRequest() *request.Request {
  function NewHTTPRequest (line 35) | func NewHTTPRequest() *request.Request {
  type RequestExpect (line 39) | type RequestExpect struct
    method Request (line 58) | func (e *RequestExpect) Request(req *request.Request) *RequestExpect {
    method RequestModify (line 63) | func (e *RequestExpect) RequestModify(f func(r *request.Request)) *Req...
    method Protocol (line 68) | func (e *RequestExpect) Protocol(protocol string) *RequestExpect {
    method PortName (line 73) | func (e *RequestExpect) PortName(name string) *RequestExpect {
    method Port (line 80) | func (e *RequestExpect) Port(port int) *RequestExpect {
    method ExpectResp (line 87) | func (e *RequestExpect) ExpectResp(resp []byte) *RequestExpect {
    method ExpectError (line 92) | func (e *RequestExpect) ExpectError(expectErr bool) *RequestExpect {
    method Explain (line 97) | func (e *RequestExpect) Explain(explain ...any) *RequestExpect {
    method Ensure (line 104) | func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
    method Do (line 128) | func (e *RequestExpect) Do() (*request.Response, error) {
  function NewRequestExpect (line 48) | func NewRequestExpect(f *Framework) *RequestExpect {
  type EnsureFunc (line 102) | type EnsureFunc

FILE: test/e2e/framework/test_context.go
  type TestContextType (line 9) | type TestContextType struct
  function RegisterCommonFlags (line 25) | func RegisterCommonFlags(flags *flag.FlagSet) {
  function ValidateTestContext (line 32) | func ValidateTestContext(t *TestContextType) error {

FILE: test/e2e/framework/util.go
  function init (line 11) | func init() {

FILE: test/e2e/legacy/basic/client_server.go
  type generalTestConfigures (line 16) | type generalTestConfigures struct
  function renderBindPortConfig (line 26) | func renderBindPortConfig(protocol string) string {
  function runClientServerTest (line 37) | func runClientServerTest(f *framework.Framework, configures *generalTest...
  function defineClientServerTest (line 91) | func defineClientServerTest(desc string, f *framework.Framework, configu...

FILE: test/e2e/legacy/basic/cmd.go
  constant ConfigValidStr (line 14) | ConfigValidStr = "syntax is ok"

FILE: test/e2e/mock/server/httpserver/server.go
  type Server (line 11) | type Server struct
    method Run (line 71) | func (s *Server) Run() error {
    method Close (line 97) | func (s *Server) Close() error {
    method initListener (line 104) | func (s *Server) initListener() (err error) {
    method BindAddr (line 109) | func (s *Server) BindAddr() string {
    method BindPort (line 113) | func (s *Server) BindPort() int {
  type Option (line 21) | type Option
  function New (line 23) | func New(options ...Option) *Server {
  function WithBindAddr (line 34) | func WithBindAddr(addr string) Option {
  function WithBindPort (line 41) | func WithBindPort(port int) Option {
  function WithTLSConfig (line 48) | func WithTLSConfig(tlsConfig *tls.Config) Option {
  function WithHandler (line 55) | func WithHandler(h http.Handler) Option {
  function WithResponse (line 62) | func WithResponse(resp []byte) Option {

FILE: test/e2e/mock/server/interface.go
  type Server (line 3) | type Server interface

FILE: test/e2e/mock/server/oidcserver/oidcserver.go
  type Server (line 38) | type Server struct
    method Run (line 97) | func (s *Server) Run() error {
    method Close (line 123) | func (s *Server) Close() error {
    method BindAddr (line 130) | func (s *Server) BindAddr() string { return s.bindAddr }
    method BindPort (line 131) | func (s *Server) BindPort() int    { return s.bindPort }
    method Issuer (line 133) | func (s *Server) Issuer() string {
    method TokenEndpoint (line 137) | func (s *Server) TokenEndpoint() string {
    method TokenRequestCount (line 142) | func (s *Server) TokenRequestCount() int64 {
    method handleDiscovery (line 146) | func (s *Server) handleDiscovery(w http.ResponseWriter, _ *http.Reques...
    method handleJWKS (line 159) | func (s *Server) handleJWKS(w http.ResponseWriter, _ *http.Request) {
    method handleToken (line 176) | func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {
    method signJWT (line 233) | func (s *Server) signJWT() (string, error) {
  type Option (line 56) | type Option
  function WithBindPort (line 58) | func WithBindPort(port int) Option {
  function WithClientCredentials (line 62) | func WithClientCredentials(id, secret string) Option {
  function WithAudience (line 69) | func WithAudience(aud string) Option {
  function WithSubject (line 73) | func WithSubject(sub string) Option {
  function WithExpiresIn (line 77) | func WithExpiresIn(seconds int) Option {
  function New (line 81) | func New(options ...Option) *Server {

FILE: test/e2e/mock/server/streamserver/server.go
  type Type (line 14) | type Type
  constant TCP (line 17) | TCP  Type = "tcp"
  constant UDP (line 18) | UDP  Type = "udp"
  constant Unix (line 19) | Unix Type = "unix"
  type Server (line 22) | type Server struct
    method Run (line 76) | func (s *Server) Run() error {
    method Close (line 93) | func (s *Server) Close() error {
    method initListener (line 100) | func (s *Server) initListener() (err error) {
    method handle (line 114) | func (s *Server) handle(c net.Conn) {
    method BindAddr (line 134) | func (s *Server) BindAddr() string {
    method BindPort (line 138) | func (s *Server) BindPort() int {
  type Option (line 33) | type Option
  function New (line 35) | func New(netType Type, options ...Option) *Server {
  function WithBindAddr (line 48) | func WithBindAddr(addr string) Option {
  function WithBindPort (line 55) | func WithBindPort(port int) Option {
  function WithRespContent (line 62) | func WithRespContent(content []byte) Option {
  function WithCustomHandler (line 69) | func WithCustomHandler(handler func(net.Conn)) Option {

FILE: test/e2e/pkg/cert/generator.go
  type Artifacts (line 12) | type Artifacts struct
  type Generator (line 26) | type Generator interface
  function ValidCACert (line 38) | func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Tim...

FILE: test/e2e/pkg/cert/selfsigned.go
  type SelfSignedCertGenerator (line 21) | type SelfSignedCertGenerator struct
    method SetCA (line 29) | func (cp *SelfSignedCertGenerator) SetCA(caKey, caCert []byte) {
    method Generate (line 39) | func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artif...
    method validCACert (line 89) | func (cp *SelfSignedCertGenerator) validCACert() (bool, *rsa.PrivateKe...
  function NewPrivateKey (line 116) | func NewPrivateKey() (*rsa.PrivateKey, error) {
  function NewSignedCert (line 121) | func NewSignedCert(cfg cert.Config, key crypto.Signer, caCert *x509.Cert...
  function EncodePrivateKeyPEM (line 154) | func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
  function EncodeCertPEM (line 163) | func EncodeCertPEM(ct *x509.Certificate) []byte {

FILE: test/e2e/pkg/plugin/plugin.go
  type Handler (line 14) | type Handler
  type NewPluginRequest (line 16) | type NewPluginRequest
  function NewHTTPPluginServer (line 18) | func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler Han...

FILE: test/e2e/pkg/port/port.go
  type Allocator (line 12) | type Allocator struct
    method Get (line 35) | func (pa *Allocator) Get() int {
    method GetByName (line 39) | func (pa *Allocator) GetByName(portName string) int {
    method getByRange (line 88) | func (pa *Allocator) getByRange(from, to int) int {
    method Release (line 104) | func (pa *Allocator) Release(port int) {
  function NewAllocator (line 21) | func NewAllocator(from int, to int, mod int, index int) *Allocator {

FILE: test/e2e/pkg/port/util.go
  constant NameDelimiter (line 10) | NameDelimiter = "_"
  type NameOption (line 13) | type NameOption
  type nameBuilder (line 15) | type nameBuilder struct
    method String (line 46) | func (builder *nameBuilder) String() string {
  function unmarshalFromName (line 21) | func unmarshalFromName(name string) (*nameBuilder, error) {
  function WithRangePorts (line 54) | func WithRangePorts(from, to int) NameOption {
  function GenName (line 62) | func GenName(name string, options ...NameOption) string {

FILE: test/e2e/pkg/process/process.go
  type SafeBuffer (line 16) | type SafeBuffer struct
    method Write (line 21) | func (b *SafeBuffer) Write(p []byte) (int, error) {
    method String (line 27) | func (b *SafeBuffer) String() string {
  type Process (line 33) | type Process struct
    method Start (line 68) | func (p *Process) Start() error {
    method closeDone (line 87) | func (p *Process) closeDone() {
    method Done (line 92) | func (p *Process) Done() <-chan struct{} {
    method Stop (line 96) | func (p *Process) Stop() error {
    method ErrorOutput (line 111) | func (p *Process) ErrorOutput() string {
    method StdOutput (line 115) | func (p *Process) StdOutput() string {
    method Output (line 119) | func (p *Process) Output() string {
    method CountOutput (line 124) | func (p *Process) CountOutput(pattern string) int {
    method SetBeforeStopHandler (line 128) | func (p *Process) SetBeforeStopHandler(fn func()) {
    method WaitForOutput (line 134) | func (p *Process) WaitForOutput(pattern string, count int, timeout tim...
  function New (line 48) | func New(path string, params []string) *Process {
  function NewWithEnvs (line 52) | func NewWithEnvs(path string, params []string, envs []string) *Process {

FILE: test/e2e/pkg/request/request.go
  type Request (line 21) | type Request struct
    method Protocol (line 54) | func (r *Request) Protocol(protocol string) *Request {
    method TCP (line 59) | func (r *Request) TCP() *Request {
    method UDP (line 64) | func (r *Request) UDP() *Request {
    method HTTP (line 69) | func (r *Request) HTTP() *Request {
    method HTTPS (line 74) | func (r *Request) HTTPS() *Request {
    method Proxy (line 79) | func (r *Request) Proxy(url string) *Request {
    method Addr (line 84) | func (r *Request) Addr(addr string) *Request {
    method Port (line 89) | func (r *Request) Port(port int) *Request {
    method HTTPParams (line 94) | func (r *Request) HTTPParams(method, host, path string, headers map[st...
    method HTTPHost (line 102) | func (r *Request) HTTPHost(host string) *Request {
    method HTTPPath (line 107) | func (r *Request) HTTPPath(path string) *Request {
    method HTTPHeaders (line 112) | func (r *Request) HTTPHeaders(headers map[string]string) *Request {
    method HTTPAuth (line 117) | func (r *Request) HTTPAuth(user, password string) *Request {
    method TLSConfig (line 122) | func (r *Request) TLSConfig(tlsConfig *tls.Config) *Request {
    method Timeout (line 127) | func (r *Request) Timeout(timeout time.Duration) *Request {
    method Body (line 132) | func (r *Request) Body(content []byte) *Request {
    method Resolver (line 137) | func (r *Request) Resolver(resolver *net.Resolver) *Request {
    method Do (line 142) | func (r *Request) Do() (*Response, error) {
    method sendHTTPRequest (line 203) | func (r *Request) sendHTTPRequest(method, urlstr string, host string, ...
    method sendRequestByConn (line 257) | func (r *Request) sendRequestByConn(c net.Conn, content []byte) ([]byt...
  function New (line 43) | func New() *Request {
  type Response (line 197) | type Response struct

FILE: test/e2e/pkg/rpc/rpc.go
  function WriteBytes (line 11) | func WriteBytes(w io.Writer, buf []byte) (int, error) {
  function ReadBytes (line 21) | func ReadBytes(r io.Reader) ([]byte, error) {

FILE: test/e2e/pkg/ssh/client.go
  type TunnelClient (line 10) | type TunnelClient struct
    method Start (line 27) | func (c *TunnelClient) Start() error {
    method Close (line 63) | func (c *TunnelClient) Close() {
    method serveListener (line 72) | func (c *TunnelClient) serveListener() {
    method handleConn (line 82) | func (c *TunnelClient) handleConn(conn net.Conn) {
  function NewTunnelClient (line 19) | func NewTunnelClient(localAddr string, sshServer string, commands string...

FILE: test/e2e/suites.go
  function CleanupSuite (line 7) | func CleanupSuite() {
  function AfterSuiteActions (line 12) | func AfterSuiteActions() {

FILE: test/e2e/v1/basic/client_server.go
  type generalTestConfigures (line 16) | type generalTestConfigures struct
  function renderBindPortConfig (line 26) | func renderBindPortConfig(protocol string) string {
  function runClientServerTest (line 37) | func runClientServerTest(f *framework.Framework, configures *generalTest...
  function defineClientServerTest (line 93) | func defineClientServerTest(desc string, f *framework.Framework, configu...

FILE: test/e2e/v1/basic/cmd.go
  constant ConfigValidStr (line 14) | ConfigValidStr = "syntax is ok"

FILE: web/frpc/components.d.ts
  type GlobalComponents (line 9) | interface GlobalComponents {
  type ComponentCustomProperties (line 44) | interface ComponentCustomProperties {

FILE: web/frpc/embed.go
  function init (line 14) | func init() {

FILE: web/frpc/src/api/http.ts
  class HTTPError (line 3) | class HTTPError extends Error {
    method constructor (line 7) | constructor(status: number, statusText: string, message?: string) {
  function request (line 14) | async function request<T>(url: string, options: RequestInit = {}): Promi...

FILE: web/frpc/src/composables/useResponsive.ts
  function useResponsive (line 5) | function useResponsive() {

FILE: web/frpc/src/types/constants.ts
  constant PROXY_TYPES (line 1) | const PROXY_TYPES = [
  type ProxyType (line 12) | type ProxyType = (typeof PROXY_TYPES)[number]
  constant VISITOR_TYPES (line 14) | const VISITOR_TYPES = ['stcp', 'sudp', 'xtcp'] as const
  type VisitorType (line 16) | type VisitorType = (typeof VISITOR_TYPES)[number]
  constant PLUGIN_TYPES (line 18) | const PLUGIN_TYPES = [
  type PluginType (line 32) | type PluginType = (typeof PLUGIN_TYPES)[number]

FILE: web/frpc/src/types/proxy-converters.ts
  function formToStoreProxy (line 10) | function formToStoreProxy(form: ProxyFormData): ProxyDefinition {
  function formToStoreVisitor (line 170) | function formToStoreVisitor(form: VisitorFormData): VisitorDefinition {
  function getStoreProxyBlock (line 233) | function getStoreProxyBlock(config: ProxyDefinition): Record<string, any> {
  function withStoreProxyBlock (line 254) | function withStoreProxyBlock(
  function getStoreVisitorBlock (line 288) | function getStoreVisitorBlock(config: VisitorDefinition): Record<string,...
  function withStoreVisitorBlock (line 299) | function withStoreVisitorBlock(
  function storeProxyToForm (line 318) | function storeProxyToForm(config: ProxyDefinition): ProxyFormData {
  function storeVisitorToForm (line 428) | function storeVisitorToForm(

FILE: web/frpc/src/types/proxy-form.ts
  type ProxyFormData (line 3) | interface ProxyFormData {
  type VisitorFormData (line 65) | interface VisitorFormData {
  function createDefaultProxyForm (line 92) | function createDefaultProxyForm(): ProxyFormData {
  function createDefaultVisitorForm (line 144) | function createDefaultVisitorForm(): VisitorFormData {

FILE: web/frpc/src/types/proxy-status.ts
  type ProxyStatus (line 1) | interface ProxyStatus {
  type StatusResponse (line 13) | type StatusResponse = Record<string, ProxyStatus[]>

FILE: web/frpc/src/types/proxy-store.ts
  type ProxyDefinition (line 3) | interface ProxyDefinition {
  type VisitorDefinition (line 16) | interface VisitorDefinition {
  type ProxyListResp (line 24) | interface ProxyListResp {
  type VisitorListResp (line 28) | interface VisitorListResp {

FILE: web/frpc/src/utils/format.ts
  function formatDistanceToNow (line 1) | function formatDistanceToNow(date: Date): string {
  function formatFileSize (line 22) | function formatFileSize(bytes: number): string {

FILE: web/frps/components.d.ts
  type GlobalComponents (line 9) | interface GlobalComponents {
  type ComponentCustomProperties (line 29) | interface ComponentCustomProperties {

FILE: web/frps/embed.go
  function init (line 14) | func init() {

FILE: web/frps/src/api/http.ts
  class HTTPError (line 3) | class HTTPError extends Error {
    method constructor (line 7) | constructor(status: number, statusText: string, message?: string) {
  function request (line 14) | async function request<T>(url: string, options: RequestInit = {}): Promi...

FILE: web/frps/src/composables/useResponsive.ts
  function useResponsive (line 5) | function useResponsive() {

FILE: web/frps/src/router/index.ts
  method scrollBehavior (line 10) | scrollBehavior() {

FILE: web/frps/src/types/client.ts
  type ClientInfoData (line 1) | interface ClientInfoData {

FILE: web/frps/src/types/proxy.ts
  type ProxyStatsInfo (line 1) | interface ProxyStatsInfo {
  type GetProxyResponse (line 14) | interface GetProxyResponse {
  type TrafficResponse (line 18) | interface TrafficResponse {

FILE: web/frps/src/types/server.ts
  type ServerInfo (line 1) | interface ServerInfo {

FILE: web/frps/src/utils/client.ts
  class Client (line 4) | class Client {
    method constructor (line 18) | constructor(data: ClientInfoData) {
    method displayName (line 40) | get displayName(): string {
    method shortRunId (line 47) | get shortRunId(): string {
    method firstConnectedAgo (line 51) | get firstConnectedAgo(): string {
    method lastConnectedAgo (line 55) | get lastConnectedAgo(): string {
    method disconnectedAgo (line 59) | get disconnectedAgo(): string {
    method statusColor (line 64) | get statusColor(): string {
    method metasArray (line 68) | get metasArray(): Array<{ key: string; value: string }> {
    method matchesFilter (line 76) | matchesFilter(searchText: string): boolean {

FILE: web/frps/src/utils/format.ts
  function formatDistanceToNow (line 1) | function formatDistanceToNow(date: Date): string {
  function formatFileSize (line 22) | function formatFileSize(bytes: number): string {

FILE: web/frps/src/utils/proxy.ts
  class BaseProxy (line 1) | class BaseProxy {
    method constructor (line 27) | constructor(proxyStats: any) {
  class TCPProxy (line 63) | class TCPProxy extends BaseProxy {
    method constructor (line 64) | constructor(proxyStats: any) {
  class UDPProxy (line 77) | class UDPProxy extends BaseProxy {
    method constructor (line 78) | constructor(proxyStats: any) {
  class HTTPProxy (line 91) | class HTTPProxy extends BaseProxy {
    method constructor (line 92) | constructor(proxyStats: any, port: number, subdomainHost: string) {
  class HTTPSProxy (line 107) | class HTTPSProxy extends BaseProxy {
    method constructor (line 108) | constructor(proxyStats: any, port: number, subdomainHost: string) {
  class TCPMuxProxy (line 121) | class TCPMuxProxy extends BaseProxy {
    method constructor (line 122) | constructor(proxyStats: any, port: number, subdomainHost: string) {
  class STCPProxy (line 138) | class STCPProxy extends BaseProxy {
    method constructor (line 139) | constructor(proxyStats: any) {
  class SUDPProxy (line 145) | class SUDPProxy extends BaseProxy {
    method constructor (line 146) | constructor(proxyStats: any) {
Condensed preview — 445 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,860K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 492,
    "preview": "version: 2\njobs:\n  go-version-latest:\n    docker:\n    - image: cimg/go:1.25-node\n    resource_class: large\n    steps:\n  "
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 108,
    "preview": "# These are supported funding model platforms\n\ngithub: [fatedier]\ncustom: [\"https://afdian.com/a/fatedier\"]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "chars": 2082,
    "preview": "name: Bug report\ndescription: Report a bug to help us improve frp\n\nbody:\n- type: markdown\n  attributes:\n    value: |\n   "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 28,
    "preview": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "chars": 912,
    "preview": "name: Feature Request\ndescription: Suggest an idea to improve frp\ntitle: \"[Feature Request] \"\n\nbody:\n- type: markdown\n  "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 37,
    "preview": "### WHY\n\n<!-- author to complete -->\n"
  },
  {
    "path": ".github/workflows/build-and-push-image.yml",
    "chars": 2596,
    "preview": "name: Build Image and Publish to Dockerhub & GPR\n\non:\n  release:\n    types: [ published ]\n  workflow_dispatch:\n    input"
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "chars": 895,
    "preview": "name: golangci-lint\non:\n  push:\n    branches:\n    - master\n    - dev\n  pull_request:\npermissions:\n  contents: read\n  # O"
  },
  {
    "path": ".github/workflows/goreleaser.yml",
    "chars": 875,
    "preview": "name: goreleaser\n\non:\n  workflow_dispatch:\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Chec"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 1330,
    "preview": "name: \"Close stale issues and PRs\"\non:\n  schedule:\n  - cron: \"20 0 * * *\"\n  workflow_dispatch:\n    inputs:\n      debug-o"
  },
  {
    "path": ".gitignore",
    "chars": 316,
    "preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n*.exe\n*.test\n*.pro"
  },
  {
    "path": ".golangci.yml",
    "chars": 2080,
    "preview": "version: \"2\"\nrun:\n  concurrency: 4\n  timeout: 20m\n  build-tags:\n  - integ\n  - integfuzz\nlinters:\n  default: none\n  enabl"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 596,
    "preview": "builds:\n  - skip: true\nchecksum:\n  name_template: '{{ .ProjectName }}_sha256_checksums.txt'\n  algorithm: sha256\n  extra_"
  },
  {
    "path": "AGENTS.md",
    "chars": 943,
    "preview": "# AGENTS.md\n\n## Development Commands\n\n### Build\n- `make build` - Build both frps and frpc binaries\n- `make frps` - Build"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 1627,
    "preview": "export PATH := $(PATH):`go env GOPATH`/bin\nexport GO111MODULE=on\nLDFLAGS := -s -w\nNOWEB_TAG = $(shell [ ! -d web/frps/di"
  },
  {
    "path": "Makefile.cross-compiles",
    "chars": 1740,
    "preview": "export PATH := $(PATH):`go env GOPATH`/bin\nexport GO111MODULE=on\nLDFLAGS := -s -w\n\nos-archs=darwin:amd64 darwin:arm64 fr"
  },
  {
    "path": "README.md",
    "chars": 44379,
    "preview": "# frp\n\n[![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp)\n[!"
  },
  {
    "path": "README_zh.md",
    "chars": 4669,
    "preview": "# frp\n\n[![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp)\n[!"
  },
  {
    "path": "Release.md",
    "chars": 1167,
    "preview": "## Features\n\n* Added a built-in `store` capability for frpc, including persisted store source (`[store] path = \"...\"`), "
  },
  {
    "path": "assets/assets.go",
    "chars": 1407,
    "preview": "// Copyright 2016 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/api_router.go",
    "chars": 4368,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/config_manager.go",
    "chars": 12985,
    "preview": "package client\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/fatedier/frp/client/configmgmt\"\n\t\"github.com/fated"
  },
  {
    "path": "client/config_manager_test.go",
    "chars": 3614,
    "preview": "package client\n\nimport (\n\t\"errors\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/fatedier/frp/client/configmgmt\"\n\t\"github.co"
  },
  {
    "path": "client/configmgmt/types.go",
    "chars": 1404,
    "preview": "package configmgmt\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/fatedier/frp/client/proxy\"\n\tv1 \"github.com/fatedier/frp/pkg"
  },
  {
    "path": "client/connector.go",
    "chars": 6890,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/control.go",
    "chars": 9100,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/event/event.go",
    "chars": 293,
    "preview": "package event\n\nimport (\n\t\"errors\"\n\n\t\"github.com/fatedier/frp/pkg/msg\"\n)\n\nvar ErrPayloadType = errors.New(\"error payload "
  },
  {
    "path": "client/health/health.go",
    "chars": 4351,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/http/controller.go",
    "chars": 12315,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/http/controller_test.go",
    "chars": 19397,
    "preview": "package http\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t"
  },
  {
    "path": "client/http/model/proxy_definition.go",
    "chars": 3243,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tv1 \"github.com/fatedier/frp/pkg/config/v1\"\n)\n\ntype ProxyDefinition struct {\n"
  },
  {
    "path": "client/http/model/types.go",
    "chars": 1412,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/http/model/visitor_definition.go",
    "chars": 2386,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tv1 \"github.com/fatedier/frp/pkg/config/v1\"\n)\n\ntype VisitorDefinition struct "
  },
  {
    "path": "client/proxy/general_tcp.go",
    "chars": 1345,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/proxy/proxy.go",
    "chars": 7676,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/proxy/proxy_manager.go",
    "chars": 4409,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/proxy/proxy_wrapper.go",
    "chars": 7116,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/proxy/sudp.go",
    "chars": 4520,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/proxy/udp.go",
    "chars": 4142,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/proxy/xtcp.go",
    "chars": 6157,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "client/service.go",
    "chars": 16696,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/service_test.go",
    "chars": 6380,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sam"
  },
  {
    "path": "client/visitor/stcp.go",
    "chars": 2062,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/visitor/sudp.go",
    "chars": 5460,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/visitor/visitor.go",
    "chars": 5803,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/visitor/visitor_manager.go",
    "chars": 5500,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "client/visitor/xtcp.go",
    "chars": 12035,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "cmd/frpc/main.go",
    "chars": 818,
    "preview": "// Copyright 2016 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "cmd/frpc/sub/admin.go",
    "chars": 3659,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "cmd/frpc/sub/nathole.go",
    "chars": 2973,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "cmd/frpc/sub/proxy.go",
    "chars": 4319,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "cmd/frpc/sub/root.go",
    "chars": 6647,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "cmd/frpc/sub/verify.go",
    "chars": 1626,
    "preview": "// Copyright 2021 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "cmd/frps/main.go",
    "chars": 815,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "cmd/frps/root.go",
    "chars": 3566,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "cmd/frps/verify.go",
    "chars": 1619,
    "preview": "// Copyright 2021 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "conf/frpc.toml",
    "chars": 142,
    "preview": "serverAddr = \"127.0.0.1\"\nserverPort = 7000\n\n[[proxies]]\nname = \"test-tcp\"\ntype = \"tcp\"\nlocalIP = \"127.0.0.1\"\nlocalPort ="
  },
  {
    "path": "conf/frpc_full_example.toml",
    "chars": 15789,
    "preview": "# This configuration file is for reference only. Please do not use this configuration directly to run the program as it "
  },
  {
    "path": "conf/frps.toml",
    "chars": 16,
    "preview": "bindPort = 7000\n"
  },
  {
    "path": "conf/frps_full_example.toml",
    "chars": 6360,
    "preview": "# This configuration file is for reference only. Please do not use this configuration directly to run the program as it "
  },
  {
    "path": "conf/legacy/frpc_legacy_full.ini",
    "chars": 12668,
    "preview": "# [common] is integral section\n[common]\n# A literal address or host name for IPv6 must be enclosed\n# in square brackets,"
  },
  {
    "path": "conf/legacy/frps_legacy_full.ini",
    "chars": 5933,
    "preview": "# [common] is integral section\n[common]\n# A literal address or host name for IPv6 must be enclosed\n# in square brackets,"
  },
  {
    "path": "doc/server_plugin.md",
    "chars": 5700,
    "preview": "### Server Plugin\n\nfrp server plugin is aimed to extend frp's ability without modifying the Golang code.\n\nAn external se"
  },
  {
    "path": "doc/ssh_tunnel_gateway.md",
    "chars": 6843,
    "preview": "### SSH Tunnel Gateway\n\n*Added in v0.53.0*\n\n### Concept\n\nSSH supports reverse proxy capabilities [rfc](https://www.rfc-e"
  },
  {
    "path": "doc/virtual_net.md",
    "chars": 2169,
    "preview": "# Virtual Network (VirtualNet)\n\n*Alpha feature added in v0.62.0*\n\nThe VirtualNet feature enables frp to create and manag"
  },
  {
    "path": "dockerfiles/Dockerfile-for-frpc",
    "chars": 452,
    "preview": "FROM node:22 AS web-builder\n\nWORKDIR /web/frpc\nCOPY web/frpc/ ./\nRUN npm install\nRUN npm run build\n\nFROM golang:1.25 AS "
  },
  {
    "path": "dockerfiles/Dockerfile-for-frps",
    "chars": 452,
    "preview": "FROM node:22 AS web-builder\n\nWORKDIR /web/frps\nCOPY web/frps/ ./\nRUN npm install\nRUN npm run build\n\nFROM golang:1.25 AS "
  },
  {
    "path": "go.mod",
    "chars": 3390,
    "preview": "module github.com/fatedier/frp\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5\n\tgit"
  },
  {
    "path": "go.sum",
    "chars": 26641,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/Azure/go-ntlmssp v0.0.0-20"
  },
  {
    "path": "hack/download.sh",
    "chars": 1676,
    "preview": "#!/bin/sh\n\nOS=\"$(go env GOOS)\"\nARCH=\"$(go env GOARCH)\"\n\nif [ \"${TARGET_OS}\" ]; then\n  OS=\"${TARGET_OS}\"\nfi\nif [ \"${TARGE"
  },
  {
    "path": "hack/run-e2e.sh",
    "chars": 836,
    "preview": "#!/bin/sh\n\nSCRIPT=$(readlink -f \"$0\")\nROOT=$(unset CDPATH && cd \"$(dirname \"$SCRIPT\")/..\" && pwd)\n\n# Check if ginkgo is "
  },
  {
    "path": "package.sh",
    "chars": 2070,
    "preview": "#!/bin/sh\nset -e\n\n# compile for version\nmake\nif [ $? -ne 0 ]; then\n    echo \"make error\"\n    exit 1\nfi\n\nfrp_version=`./b"
  },
  {
    "path": "pkg/auth/auth.go",
    "chars": 3570,
    "preview": "// Copyright 2020 guylewin, guy@lewin.co.il\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
  },
  {
    "path": "pkg/auth/legacy/legacy.go",
    "chars": 6012,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/auth/oidc.go",
    "chars": 10321,
    "preview": "// Copyright 2020 guylewin, guy@lewin.co.il\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
  },
  {
    "path": "pkg/auth/oidc_test.go",
    "chars": 7776,
    "preview": "package auth_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"tim"
  },
  {
    "path": "pkg/auth/pass.go",
    "chars": 956,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/auth/token.go",
    "chars": 2852,
    "preview": "// Copyright 2020 guylewin, guy@lewin.co.il\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
  },
  {
    "path": "pkg/config/flags.go",
    "chars": 11031,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/README.md",
    "chars": 835,
    "preview": "So far, there is no mature Go project that does well in parsing `*.ini` files. \n\nBy comparison, we have selected an open"
  },
  {
    "path": "pkg/config/legacy/client.go",
    "chars": 15951,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/conversion.go",
    "chars": 12421,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/parse.go",
    "chars": 2572,
    "preview": "// Copyright 2021 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/proxy.go",
    "chars": 12414,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/server.go",
    "chars": 13372,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/utils.go",
    "chars": 1163,
    "preview": "// Copyright 2020 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/value.go",
    "chars": 1526,
    "preview": "// Copyright 2020 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/legacy/visitor.go",
    "chars": 5386,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/load.go",
    "chars": 14795,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/load_test.go",
    "chars": 15557,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/aggregator.go",
    "chars": 2803,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/aggregator_test.go",
    "chars": 6528,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/base_source.go",
    "chars": 1999,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/base_source_test.go",
    "chars": 1102,
    "preview": "package source\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\tv1 \"github.com/fatedier/frp/pkg/config/v1\""
  },
  {
    "path": "pkg/config/source/clone.go",
    "chars": 1324,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/config_source.go",
    "chars": 1784,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/config_source_test.go",
    "chars": 4930,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/source.go",
    "chars": 1585,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/store.go",
    "chars": 8425,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/source/store_test.go",
    "chars": 3582,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/template.go",
    "chars": 1493,
    "preview": "// Copyright 2024 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/types/types.go",
    "chars": 3951,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/config/types/types_test.go",
    "chars": 2248,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/config/v1/api.go",
    "chars": 666,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/client.go",
    "chars": 11169,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/client_test.go",
    "chars": 1349,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/clone_test.go",
    "chars": 3262,
    "preview": "package v1\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestProxyCloneDeepCopy(t *testing.T) {\n\t"
  },
  {
    "path": "pkg/config/v1/common.go",
    "chars": 4838,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/decode.go",
    "chars": 5953,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/decode_test.go",
    "chars": 2258,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/proxy.go",
    "chars": 15807,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/proxy_plugin.go",
    "chars": 7370,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/proxy_test.go",
    "chars": 1282,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/server.go",
    "chars": 9305,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/server_test.go",
    "chars": 1186,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/store.go",
    "chars": 893,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/client.go",
    "chars": 6429,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/common.go",
    "chars": 1485,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/oidc.go",
    "chars": 1799,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/oidc_test.go",
    "chars": 2541,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/plugin.go",
    "chars": 2254,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/proxy.go",
    "chars": 8132,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/server.go",
    "chars": 2703,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/validation.go",
    "chars": 1417,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/validation/validator.go",
    "chars": 809,
    "preview": "package validation\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/fatedier/frp/pkg/policy/security\"\n)\n\n// ConfigValidator holds the cont"
  },
  {
    "path": "pkg/config/v1/validation/visitor.go",
    "chars": 1572,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/value_source.go",
    "chars": 4154,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/value_source_test.go",
    "chars": 4938,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/visitor.go",
    "chars": 4911,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/config/v1/visitor_plugin.go",
    "chars": 1861,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/errors/errors.go",
    "chars": 749,
    "preview": "// Copyright 2016 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/metrics/aggregate/server.go",
    "chars": 2316,
    "preview": "// Copyright 2020 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/metrics/mem/server.go",
    "chars": 7009,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/metrics/mem/types.go",
    "chars": 2144,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/metrics/metrics.go",
    "chars": 766,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/metrics/prometheus/server.go",
    "chars": 3303,
    "preview": "package prometheus\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\n\t\"github.com/fatedier/frp/server/metrics"
  },
  {
    "path": "pkg/msg/ctl.go",
    "chars": 1132,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/msg/handler.go",
    "chars": 2180,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/msg/msg.go",
    "chars": 8946,
    "preview": "// Copyright 2016 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/naming/names.go",
    "chars": 870,
    "preview": "package naming\n\nimport \"strings\"\n\n// AddUserPrefix builds the wire-level proxy name for frps by prefixing user.\nfunc Add"
  },
  {
    "path": "pkg/naming/names_test.go",
    "chars": 823,
    "preview": "package naming\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAddUserPrefix(t *testing.T) {\n\tr"
  },
  {
    "path": "pkg/nathole/analysis.go",
    "chars": 12580,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/nathole/classify.go",
    "chars": 2908,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/nathole/controller.go",
    "chars": 11585,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/nathole/discovery.go",
    "chars": 4379,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/nathole/nathole.go",
    "chars": 13495,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/nathole/utils.go",
    "chars": 2416,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/plugin/client/http2http.go",
    "chars": 2187,
    "preview": "// Copyright 2024 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/plugin/client/http2https.go",
    "chars": 2540,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/http_proxy.go",
    "chars": 5672,
    "preview": "// Copyright 2017 frp team\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this"
  },
  {
    "path": "pkg/plugin/client/https2http.go",
    "chars": 3191,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/https2https.go",
    "chars": 3308,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/plugin.go",
    "chars": 2514,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/socks5.go",
    "chars": 1719,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/static_file.go",
    "chars": 2137,
    "preview": "// Copyright 2018 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/tls2raw.go",
    "chars": 2039,
    "preview": "// Copyright 2024 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/plugin/client/unix_domain_socket.go",
    "chars": 1947,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/client/virtual_net.go",
    "chars": 2344,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/plugin/server/http.go",
    "chars": 2736,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/server/manager.go",
    "chars": 6720,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/server/plugin.go",
    "chars": 1012,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/server/tracer.go",
    "chars": 922,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/server/types.go",
    "chars": 1716,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/plugin/visitor/plugin.go",
    "chars": 2082,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/plugin/visitor/virtual_net.go",
    "chars": 6602,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/policy/featuregate/feature_gate.go",
    "chars": 6056,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/policy/security/unsafe.go",
    "chars": 561,
    "preview": "package security\n\nconst (\n\tTokenSourceExec = \"TokenSourceExec\"\n)\n\nvar (\n\tClientUnsafeFeatures = []string{\n\t\tTokenSourceE"
  },
  {
    "path": "pkg/proto/udp/udp.go",
    "chars": 3618,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/proto/udp/udp_test.go",
    "chars": 302,
    "preview": "package udp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUdpPacket(t *testing.T) {\n\trequire "
  },
  {
    "path": "pkg/sdk/client/client.go",
    "chars": 3275,
    "preview": "package client\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t"
  },
  {
    "path": "pkg/ssh/gateway.go",
    "chars": 3710,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/ssh/server.go",
    "chars": 10309,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/ssh/terminal.go",
    "chars": 1052,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/transport/message.go",
    "chars": 3325,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/transport/tls.go",
    "chars": 3969,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/http/context.go",
    "chars": 1288,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/http/error.go",
    "chars": 835,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/http/handler.go",
    "chars": 1787,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/http/http.go",
    "chars": 2550,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/http/middleware.go",
    "chars": 1175,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/http/server.go",
    "chars": 2991,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/jsonx/json_v1.go",
    "chars": 1234,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/jsonx/raw_message.go",
    "chars": 1058,
    "preview": "// Copyright 2026 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/limit/reader.go",
    "chars": 1109,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/limit/writer.go",
    "chars": 1220,
    "preview": "// Copyright 2019 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/log/log.go",
    "chars": 2475,
    "preview": "// Copyright 2016 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/metric/counter.go",
    "chars": 1306,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/metric/counter_test.go",
    "chars": 361,
    "preview": "package metric\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCounter(t *testing.T) {\n\trequire"
  },
  {
    "path": "pkg/util/metric/date_counter.go",
    "chars": 3027,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/metric/date_counter_test.go",
    "chars": 524,
    "preview": "package metric\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDateCounter(t *testing.T) {\n\treq"
  },
  {
    "path": "pkg/util/metric/metrics.go",
    "chars": 998,
    "preview": "// Copyright 2020 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/net/conn.go",
    "chars": 5749,
    "preview": "// Copyright 2016 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/net/dial.go",
    "chars": 1131,
    "preview": "package net\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/url\"\n\n\tlibnet \"github.com/fatedier/golib/net\"\n\t\"golang.org/x/net/websocket"
  },
  {
    "path": "pkg/util/net/dns.go",
    "chars": 1006,
    "preview": "// Copyright 2023 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/net/http.go",
    "chars": 2423,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/net/kcp.go",
    "chars": 2411,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/net/listener.go",
    "chars": 1799,
    "preview": "// Copyright 2017 fatedier, fatedier@gmail.com\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// "
  },
  {
    "path": "pkg/util/net/proxyprotocol.go",
    "chars": 1317,
    "preview": "// Copyright 2025 The frp Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not u"
  },
  {
    "path": "pkg/util/net/proxyprotocol_test.go",
    "chars": 5871,
    "preview": "package net\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\tpp \"github.com/pires/go-proxyproto\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n"
  }
]

// ... and 245 more files (download for full content)

About this extraction

This page contains the full source code of the fatedier/frp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 445 files (1.6 MB), approximately 479.0k tokens, and a symbol index with 2183 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!