py-simple-nat-tunnel 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GGN_2015
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-simple-nat-tunnel
3
+ Version: 0.1.0
4
+ Summary: A small Python TCP reverse tunnel compatible with common frp/rathole config subsets.
5
+ Author: GGN_2015
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/GGN-2015/py-frp
8
+ Project-URL: Repository, https://github.com/GGN-2015/py-frp
9
+ Project-URL: Issues, https://github.com/GGN-2015/py-frp/issues
10
+ Keywords: frp,rathole,reverse-proxy,tunnel,nat-traversal
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Internet :: Proxy Servers
23
+ Classifier: Topic :: System :: Networking
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: py-admin-launch>=0.1.3
28
+ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
29
+ Dynamic: license-file
30
+
31
+ # py-frp
32
+
33
+ 一个小型 Python 生态内网穿透工具,设计上参考 frp/rathole 的“服务端公网监听 + 客户端反向拨入”模型。当前版本专注 TCP 端口映射,支持服务端、客户端、frp 常见 TOML/INI 配置子集,以及 rathole 常见 TOML 配置子集。
34
+
35
+ ## 安装
36
+
37
+ 从 PyPI 安装:
38
+
39
+ ```bash
40
+ pip install py-simple-nat-tunnel
41
+ ```
42
+
43
+ 从源码安装:
44
+
45
+ ```bash
46
+ pip install .
47
+ ```
48
+
49
+ 开发模式安装:
50
+
51
+ ```bash
52
+ pip install -e .
53
+ ```
54
+
55
+ 安装后会得到这些命令:
56
+
57
+ ```bash
58
+ py-frp --version
59
+ py-frp server -c examples/frps.toml
60
+ py-frp client -c examples/frpc.toml
61
+ py-frps -c examples/frps.toml
62
+ py-frpc -c examples/frpc.toml
63
+ ```
64
+
65
+ Windows 上如果提示 scripts 目录不在 `PATH`,也可以直接用:
66
+
67
+ ```bash
68
+ python -m py_frp server -c examples/frps.toml
69
+ python -m py_frp client -c examples/frpc.toml
70
+ ```
71
+
72
+ ## 快速开始
73
+
74
+ 服务端公网机器:
75
+
76
+ ```bash
77
+ py-frp server -c examples/frps.toml
78
+ ```
79
+
80
+ 客户端内网机器:
81
+
82
+ ```bash
83
+ py-frp client -c examples/frpc.toml
84
+ ```
85
+
86
+ `examples/frpc.toml` 默认把客户端的 `127.0.0.1:22` 暴露到服务端的 `0.0.0.0:6000`。启动后从外部访问:
87
+
88
+ ```bash
89
+ ssh -p 6000 user@your-server
90
+ ```
91
+
92
+ ## frp 配置子集
93
+
94
+ 服务端 `frps.toml`:
95
+
96
+ ```toml
97
+ bindPort = 7000
98
+
99
+ [auth]
100
+ token = "change-me"
101
+ ```
102
+
103
+ 客户端 `frpc.toml`:
104
+
105
+ ```toml
106
+ serverAddr = "your-server"
107
+ serverPort = 7000
108
+
109
+ [auth]
110
+ token = "change-me"
111
+
112
+ [[proxies]]
113
+ name = "ssh"
114
+ type = "tcp"
115
+ localIP = "127.0.0.1"
116
+ localPort = 22
117
+ remotePort = 6000
118
+ ```
119
+
120
+ 也支持旧版 frp INI 的 `[common]` 和 TCP proxy section:
121
+
122
+ ```ini
123
+ [common]
124
+ server_addr = your-server
125
+ server_port = 7000
126
+ token = change-me
127
+
128
+ [ssh]
129
+ type = tcp
130
+ local_ip = 127.0.0.1
131
+ local_port = 22
132
+ remote_port = 6000
133
+ ```
134
+
135
+ ## rathole 配置子集
136
+
137
+ 服务端:
138
+
139
+ ```toml
140
+ [server]
141
+ bind_addr = "0.0.0.0:2333"
142
+ default_token = "change-me"
143
+
144
+ [server.services.ssh]
145
+ bind_addr = "0.0.0.0:6000"
146
+ ```
147
+
148
+ 客户端:
149
+
150
+ ```toml
151
+ [client]
152
+ remote_addr = "your-server:2333"
153
+ default_token = "change-me"
154
+
155
+ [client.services.ssh]
156
+ local_addr = "127.0.0.1:22"
157
+ ```
158
+
159
+ ## 提权
160
+
161
+ 服务端启动时如果配置里有小于 1024 的监听端口,默认会通过 `py-admin-launch` 尝试提权一次。也可以显式控制:
162
+
163
+ ```bash
164
+ py-frp server -c frps.toml --elevate
165
+ py-frp server -c frps.toml --no-auto-elevate
166
+ ```
167
+
168
+ 实现上会在重启命令后追加内部标记,保证最多只尝试一次,不会循环弹出提权请求。
169
+
170
+ ## 当前范围
171
+
172
+ 已支持:
173
+
174
+ - TCP 反向端口映射
175
+ - 多服务/多 proxy
176
+ - token 鉴权
177
+ - frp TOML 和 legacy INI 的常见 TCP 配置
178
+ - rathole TOML 的常见 TCP 配置
179
+ - `pip install .` 一键安装和 console scripts
180
+
181
+ 暂不支持:
182
+
183
+ - UDP
184
+ - HTTP/HTTPS vhost
185
+ - frp 插件、负载均衡、STCP/XTCP
186
+ - rathole Noise/TLS 加密传输
187
+ - 多路复用;当前每个公网连接会建立一条独立 tunnel TCP
188
+
189
+ ## 测试
190
+
191
+ ```bash
192
+ python -m unittest discover -s tests -v
193
+ ```
@@ -0,0 +1,163 @@
1
+ # py-frp
2
+
3
+ 一个小型 Python 生态内网穿透工具,设计上参考 frp/rathole 的“服务端公网监听 + 客户端反向拨入”模型。当前版本专注 TCP 端口映射,支持服务端、客户端、frp 常见 TOML/INI 配置子集,以及 rathole 常见 TOML 配置子集。
4
+
5
+ ## 安装
6
+
7
+ 从 PyPI 安装:
8
+
9
+ ```bash
10
+ pip install py-simple-nat-tunnel
11
+ ```
12
+
13
+ 从源码安装:
14
+
15
+ ```bash
16
+ pip install .
17
+ ```
18
+
19
+ 开发模式安装:
20
+
21
+ ```bash
22
+ pip install -e .
23
+ ```
24
+
25
+ 安装后会得到这些命令:
26
+
27
+ ```bash
28
+ py-frp --version
29
+ py-frp server -c examples/frps.toml
30
+ py-frp client -c examples/frpc.toml
31
+ py-frps -c examples/frps.toml
32
+ py-frpc -c examples/frpc.toml
33
+ ```
34
+
35
+ Windows 上如果提示 scripts 目录不在 `PATH`,也可以直接用:
36
+
37
+ ```bash
38
+ python -m py_frp server -c examples/frps.toml
39
+ python -m py_frp client -c examples/frpc.toml
40
+ ```
41
+
42
+ ## 快速开始
43
+
44
+ 服务端公网机器:
45
+
46
+ ```bash
47
+ py-frp server -c examples/frps.toml
48
+ ```
49
+
50
+ 客户端内网机器:
51
+
52
+ ```bash
53
+ py-frp client -c examples/frpc.toml
54
+ ```
55
+
56
+ `examples/frpc.toml` 默认把客户端的 `127.0.0.1:22` 暴露到服务端的 `0.0.0.0:6000`。启动后从外部访问:
57
+
58
+ ```bash
59
+ ssh -p 6000 user@your-server
60
+ ```
61
+
62
+ ## frp 配置子集
63
+
64
+ 服务端 `frps.toml`:
65
+
66
+ ```toml
67
+ bindPort = 7000
68
+
69
+ [auth]
70
+ token = "change-me"
71
+ ```
72
+
73
+ 客户端 `frpc.toml`:
74
+
75
+ ```toml
76
+ serverAddr = "your-server"
77
+ serverPort = 7000
78
+
79
+ [auth]
80
+ token = "change-me"
81
+
82
+ [[proxies]]
83
+ name = "ssh"
84
+ type = "tcp"
85
+ localIP = "127.0.0.1"
86
+ localPort = 22
87
+ remotePort = 6000
88
+ ```
89
+
90
+ 也支持旧版 frp INI 的 `[common]` 和 TCP proxy section:
91
+
92
+ ```ini
93
+ [common]
94
+ server_addr = your-server
95
+ server_port = 7000
96
+ token = change-me
97
+
98
+ [ssh]
99
+ type = tcp
100
+ local_ip = 127.0.0.1
101
+ local_port = 22
102
+ remote_port = 6000
103
+ ```
104
+
105
+ ## rathole 配置子集
106
+
107
+ 服务端:
108
+
109
+ ```toml
110
+ [server]
111
+ bind_addr = "0.0.0.0:2333"
112
+ default_token = "change-me"
113
+
114
+ [server.services.ssh]
115
+ bind_addr = "0.0.0.0:6000"
116
+ ```
117
+
118
+ 客户端:
119
+
120
+ ```toml
121
+ [client]
122
+ remote_addr = "your-server:2333"
123
+ default_token = "change-me"
124
+
125
+ [client.services.ssh]
126
+ local_addr = "127.0.0.1:22"
127
+ ```
128
+
129
+ ## 提权
130
+
131
+ 服务端启动时如果配置里有小于 1024 的监听端口,默认会通过 `py-admin-launch` 尝试提权一次。也可以显式控制:
132
+
133
+ ```bash
134
+ py-frp server -c frps.toml --elevate
135
+ py-frp server -c frps.toml --no-auto-elevate
136
+ ```
137
+
138
+ 实现上会在重启命令后追加内部标记,保证最多只尝试一次,不会循环弹出提权请求。
139
+
140
+ ## 当前范围
141
+
142
+ 已支持:
143
+
144
+ - TCP 反向端口映射
145
+ - 多服务/多 proxy
146
+ - token 鉴权
147
+ - frp TOML 和 legacy INI 的常见 TCP 配置
148
+ - rathole TOML 的常见 TCP 配置
149
+ - `pip install .` 一键安装和 console scripts
150
+
151
+ 暂不支持:
152
+
153
+ - UDP
154
+ - HTTP/HTTPS vhost
155
+ - frp 插件、负载均衡、STCP/XTCP
156
+ - rathole Noise/TLS 加密传输
157
+ - 多路复用;当前每个公网连接会建立一条独立 tunnel TCP
158
+
159
+ ## 测试
160
+
161
+ ```bash
162
+ python -m unittest discover -s tests -v
163
+ ```
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "py-simple-nat-tunnel"
7
+ version = "0.1.0"
8
+ description = "A small Python TCP reverse tunnel compatible with common frp/rathole config subsets."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "GGN_2015" }
14
+ ]
15
+ keywords = ["frp", "rathole", "reverse-proxy", "tunnel", "nat-traversal"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Framework :: AsyncIO",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Internet :: Proxy Servers",
29
+ "Topic :: System :: Networking"
30
+ ]
31
+ dependencies = [
32
+ "py-admin-launch>=0.1.3",
33
+ "tomli>=2.0.1; python_version < '3.11'"
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/GGN-2015/py-frp"
38
+ Repository = "https://github.com/GGN-2015/py-frp"
39
+ Issues = "https://github.com/GGN-2015/py-frp/issues"
40
+
41
+ [project.scripts]
42
+ py-frp = "py_frp.cli:main"
43
+ pyfrp = "py_frp.cli:main"
44
+ py-frps = "py_frp.cli:server_main"
45
+ py-frpc = "py_frp.cli:client_main"
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+
50
+ [tool.setuptools.package-data]
51
+ py_frp = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """A small asyncio-based TCP reverse tunnel."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = ["__version__"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from .cli import main
4
+
5
+
6
+ if __name__ == "__main__":
7
+ raise SystemExit(main())
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ import logging
6
+ import sys
7
+ from collections.abc import Sequence
8
+
9
+ from . import __version__
10
+ from .client import Client
11
+ from .config import (
12
+ ConfigError,
13
+ describe_client_config,
14
+ describe_server_config,
15
+ load_client_config,
16
+ load_server_config,
17
+ )
18
+ from .elevate import ElevationError, is_admin, relaunch_once, should_elevate_server
19
+ from .server import Server
20
+
21
+
22
+ LOGGER = logging.getLogger(__name__)
23
+
24
+
25
+ def main(argv: Sequence[str] | None = None) -> int:
26
+ effective_argv = list(sys.argv[1:] if argv is None else argv)
27
+ parser = build_parser()
28
+ args = parser.parse_args(effective_argv)
29
+ _configure_logging(args.log_level)
30
+
31
+ try:
32
+ if args.command == "server":
33
+ return _run_server_command(args, effective_argv)
34
+ if args.command == "client":
35
+ return _run_client_command(args)
36
+ except KeyboardInterrupt:
37
+ return 130
38
+ except ConfigError as exc:
39
+ LOGGER.error("configuration error: %s", exc)
40
+ return 2
41
+ except ElevationError as exc:
42
+ LOGGER.error("elevation failed: %s", exc)
43
+ return 1
44
+ return 2
45
+
46
+
47
+ def server_main(argv: Sequence[str] | None = None) -> int:
48
+ return main(["server", *(sys.argv[1:] if argv is None else argv)])
49
+
50
+
51
+ def client_main(argv: Sequence[str] | None = None) -> int:
52
+ return main(["client", *(sys.argv[1:] if argv is None else argv)])
53
+
54
+
55
+ def build_parser() -> argparse.ArgumentParser:
56
+ parser = argparse.ArgumentParser(
57
+ prog="py-frp",
58
+ description="A small Python TCP reverse tunnel.",
59
+ )
60
+ parser.add_argument("--version", action="version", version=f"py-frp {__version__}")
61
+ subparsers = parser.add_subparsers(dest="command", required=True)
62
+
63
+ server = subparsers.add_parser("server", aliases=["frps"], help="run the public server")
64
+ _add_runtime_options(server)
65
+ server.add_argument("-c", "--config", required=True, help="server config path")
66
+ server.add_argument(
67
+ "--elevate",
68
+ action="store_true",
69
+ help="relaunch as administrator/root once before binding ports",
70
+ )
71
+ server.add_argument(
72
+ "--auto-elevate",
73
+ action=argparse.BooleanOptionalAction,
74
+ default=True,
75
+ help="auto elevate once when configured listen ports are privileged",
76
+ )
77
+ server.add_argument("--elevation-attempted", action="store_true", help=argparse.SUPPRESS)
78
+
79
+ client = subparsers.add_parser("client", aliases=["frpc"], help="run the private client")
80
+ _add_runtime_options(client)
81
+ client.add_argument("-c", "--config", required=True, help="client config path")
82
+
83
+ return parser
84
+
85
+
86
+ def _add_runtime_options(parser: argparse.ArgumentParser) -> None:
87
+ parser.add_argument(
88
+ "--log-level",
89
+ default="INFO",
90
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
91
+ help="logging level",
92
+ )
93
+
94
+
95
+ def _run_server_command(args: argparse.Namespace, effective_argv: Sequence[str]) -> int:
96
+ config = load_server_config(args.config)
97
+ LOGGER.info("loaded server config: %s", describe_server_config(config))
98
+ needs_elevation = args.elevate or (args.auto_elevate and should_elevate_server(config))
99
+ if needs_elevation and not args.elevation_attempted and not is_admin():
100
+ LOGGER.info("relaunching with administrator/root privileges")
101
+ return relaunch_once(effective_argv)
102
+ return asyncio.run(Server(config).serve_forever())
103
+
104
+
105
+ def _run_client_command(args: argparse.Namespace) -> int:
106
+ config = load_client_config(args.config)
107
+ LOGGER.info("loaded client config: %s", describe_client_config(config))
108
+ return asyncio.run(Client(config).run())
109
+
110
+
111
+ def _configure_logging(level: str) -> None:
112
+ logging.basicConfig(
113
+ level=getattr(logging, level),
114
+ format="%(asctime)s %(levelname)s %(name)s: %(message)s",
115
+ )