使用frp进行内网穿透

Recently, I thought about a Raspberry Pi gathering dust, so today I experimented with frp for internal network penetration, allowing my Raspberry Pi at home to be accessed via the Internet.

Prerequisites:

  1. One machine with a public IP address (either a broadband connection with a public IP or a VPS)
  2. A domain name (optional)

Environment Introduction

First, we need to configure and start the frp server on a machine with a public IP. I used my own VPS, and all operations related to the frp server were executed on my VPS. The system environment is as follows:

1
2
3
4
5
6
root@visionsmile:~# lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.9 (jessie)
Release: 8.9
Codename: jessie

Download frp

Next, download frp. The frp project provides binary programs for many platforms, which can be downloaded as needed here.
I need to download the amd64 version:

1
$ wget https://github.com/fatedier/frp/releases/download/v0.20.0/frp_0.20.0_linux_amd64.tar.gz

After downloading, extract it:

1
2
$ tar -xvzf frp_0.20.0_linux_amd64.tar.gz
$ cd frp_0.20.0_linux_amd64

frp Configuration and Startup

At this point, the directory structure looks like this:

1
2
root:~/downloads/frp_0.20.0_linux_amd64# ls
frpc frpc_full.ini frpc.ini frps frps_full.ini frps.ini LICENSE

frps is the server program for frp, and frps.ini is the configuration for the server. All configuration options for frps.ini can be found here: frps_full.ini.
The default configuration for the current version of frps.ini is as follows:

1
2
3
$ cat frps.ini
[common]
bind_port = 7000

Here, bind_port is the port that we will use to connect frpc (the frp client) to the server, so please ensure that this port is allowed through the firewall (iptables).

Note: If it is an Alibaba Cloud server, you need to set the port to be allowed in the web security group; using ufw or iptables directly on the server will not work.

I won’t write anything too complicated here; the default settings are sufficient. For more features, you can read the project’s frp/README.

Then you can start frp:

1
$ ./frps -c frps.ini

After execution, it appears as follows:

Configure frps to Start on Boot

There are two ways to configure startup on Linux: one is to add the startup command to /etc/ec.local, and the other is to use systemd to create a service. Here, I will introduce both methods, but I recommend using systemd.

First, copy frp to the /etc directory on the server:

1
2
3
$ sudo mkdir /etc/frp
# Execute in the directory where frp is extracted
$ sudo cp * /etc/frp

Using systemd

Next, create a service for startup. I am using systemd to start the frps service:

1
2
$ sudo cd /etc/systemd/system
$ sudo nano frps.service

Fill in the following content:

1
2
3
4
5
6
7
[Unit]
Description=frps daemon
[Service]
Type=idle
ExecStart=/usr/bin/frp/frps -c /usr/bin/frp/frps.ini
[Install]
WantedBy=multi-user.target

Save and exit, then modify permissions:

1
$ sudo chmod 755 frps.service

Now you can start it:

1
2
3
$ sudo systemctl start frps
# Set frps service to auto-start
$ sudo systemctl enable frps

Using /etc/rc.local

Since the server actively receives connections and does not need to depend on the startup sequence of other services, you can also directly write it to /etc/rc.local. The client is a bit different; I will explain that later.
Add the startup command (after moving the frp directory to /etc/frp):

1
2
3
4
$ sudo nano /etc/rc.local
# Add the following content before exit 0
# Start frps at boot
nohup /etc/frp/frps -c /etc/frp/frps.ini >/dev/null 2>&1 &

Use Domain Name to Resolve Server Address (Optional)

The reason for using a domain name is that if our server encounters issues, the client will also be of no use. However, I want to be able to use a different server without changing the client if this server goes down.
If we directly fill in the server’s IP address in frpc, we won’t be able to achieve that, so I thought of using a domain name that resolves to the server address I want to connect to.
So, we use a domain name (or subdomain) that resolves to the server address we want to connect to: for instance, I used dnspod to resolve frp.imzlp.com to the server XXX.XXX.XXX.XXX. After making the changes, don’t forget to ping to check if it’s reachable.

Using the same method as downloading the frp server, download the frp client. Note that the following commands are executed on the client machine (Raspberry Pi).

The default configuration for the frp client is as follows:

1
2
3
4
5
6
7
8
9
10
$ cat frpc.ini
[common]
server_addr = 127.0.0.1
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

The only thing that needs to be modified temporarily is server_addr and server_port. Since I used the default port above, I only need to change server_addr here.
Change it to my custom domain name (if you don’t have a domain name, you can directly specify the server’s IP address), as follows:

1
2
3
4
5
6
7
8
9
10
$ cat frpc.ini
[common]
server_addr = frp.imzlp.com
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

In the [ssh] label, the local_port on the client forwards to the server’s remote_port. Ensure that the remote_port is allowed through the firewall on the server; otherwise, the connection will fail.
Then you can start frpc on the client:

1
$ ./frpc -c frpc.ini

Try to Connect to Internal SSH

With the above operations, both frps and frpc have been started. We can try to connect to the Raspberry Pi’s SSH via the Internet. Since I used domain name resolution, I can use the domain name directly:

1
$ ssh -oPort=6000 pi@frp.imzlp.com

Note: Be sure to specify the port for the SSH connection on the server (i.e. the remote_port parameter in frpc.ini). If all goes well, the connection should succeed, as shown below:

Configure frpc to Start on Boot on the Client

Note: This is for Linux; scripts for Windows will be introduced later.

1
2
3
# Execute in the frp_0.20.0_linux_arm directory (configuration files have been changed)
$ sudo mkdir /etc/frp
$ sudo cp * /etc/frp

For client bootup, I am using systemd services:

1
2
$ sudo cd /etc/systemd/system
$ sudo nano frpc.service

Fill in the following content:

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=frpc daemon

[Service]
Type=idle
ExecStartPre=/bin/sleep 5
ExecStart=/etc/frp/frpc -c /etc/frp/frpc.ini

[Install]
WantedBy=multi-user.target

Pay special attention to these two lines in the script above:

1
2
Type=idle
ExecStartPre=/bin/sleep 5

The purpose of these two statements is to wait for system initialization to complete. If frp starts at boot, it may fail due to the inability to connect to the network (if using a domain name, it might fail due to DNS resolution issues).
Note: If you do not want to wait, you can remove ExecStartPre, but you are required to set login_fail_exit to false in the frpc configuration.

Start frpc and set it to start on boot:

1
2
$ systemctl start frpc
$ systemctl enable frpc

Use kcp Protocol

If you have used kcp to accelerate ss, you will know that kcp acceleration brings higher transmission efficiency and reduces latency.
The current version of frp (>v0.12.0) supports kcp at the underlying communication protocol level and can be manually enabled in the configuration file:
Server:
Enable kcp protocol support in the frps.ini on the server, specifying a UDP port to receive client requests:

1
2
3
4
5
# frps.ini
[common]
bind_port = 7000
# kcp binds the udp port, it can be the same as bind_port
kcp_bind_port = 7000

Client:
Specify the protocol type to be used in frpc.ini, which currently only supports tcp and kcp. Other proxy configurations do not need to change:

1
2
3
4
5
6
# frpc.ini
[common]
server_addr = x.x.x.x
# server_port should be set to frps's kcp_bind_port
server_port = 7000
protocol = kcp

Enable Encryption and Traffic Compression

Some local area networks may perform traffic feature recognition and interception, so frp also offers traffic encryption and compression (add to frpc.ini):

1
2
use_encryption = true
use_compression = true

Use Mstsc for Remote Connection

In my previous article, Transforming Raspberry Pi into a Portable Linux Compilation Environment, I mentioned that you can install xrdp on the Raspberry Pi so it can be connected via Windows mstsc.

1
2
# Install xrdp to allow mstsc connections
$ sudo apt-get install xrdp

When connecting via LAN, it looks like this:

Since mstsc defaults to port 3389, when we want to access via the Internet, we need to map the Raspberry Pi (client) port 3389 to the server port.
The specific operation is as follows:
Edit the client frpc.ini and add a label:

1
2
3
4
5
[xvnc]
type=tcp
local_ip=127.0.0.1
local_port=3389
remote_port=6001

This means mapping the local 3389 port to the server’s 6001 port. Save the changes and restart the frpc service:

1
$ systemctl restart frpc

Note: As mentioned above, ensure that the server’s 6001 port is not blocked by the firewall.
Once completed, you can use Mstsc to connect to the external network (again, I used the domain name):

After connecting, it is just like connecting through the local network:

After logging in:

The same method can also be used for remote connections on Windows clients, which is much stronger than anydesk or teamviewer.

It’s incredibly smooth, just like operating a local computer.
You can also use rdpwrap so that remote connections do not log out the local account when accessing other accounts (equivalent to Windows Server functionality).

Reload Configuration

frp provides a way to read changes from modified configuration files and apply new proxy updates (so you don’t have to stop and restart).
Add the following parameters to frpc.ini:

1
2
3
# frpc.ini
admin_addr = 127.0.0.1
admin_port = 7400

The command to reload is:

1
frpc reload -c ./frpc.ini

Note: The current version of frp (v0.20.0) will only reload the contents in start, such as server_addr, etc., so if server_addr has changed in frpc.ini, using reload will not reconnect to the new server.

My frp Configuration Files

1
2
3
4
5
6
#frps.ini
[common]
bind_port = 7000
kcp_bind_port = 7000
token = xxxxxxx
authentication_timeout = 900
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#frpc.ini
[common]
server_addr = frp.imzlp.com
server_port = 7000
protocol = kcp
token = xxxxxxx
login_fail_exit = false
# To avoid connection failures caused by the same proxy name on multiple clients, each client should have a different userName
user = imzlp

admin_addr = 127.0.0.1
admin_port = 7400

[SSH]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true

[XVNC]
type=tcp
local_ip=127.0.0.1
local_port=3389
remote_port=6001
use_encryption = true
use_compression = true

I wrote a Python script to detect DNS updates for my domain. If there’s an update, it restarts the client’s frpc service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Note: This script cannot be used on Windows.
import socket
import time
import os
def initFrp():
print("starting frpc service.")
os.system("sudo systemctl restart frpc.service")

def gethostbyname(url="frp.imzlp.com"):
# print(url)
ip=socket.gethostbyname(url)
# print(ip)
return ip

url="frp.imzlp.com"
ip=gethostbyname(url)
initFrp()
ExecCommandIndex=0
while True:
if(ip != gethostbyname(url)):
print("Restarting frpc service.")
os.system("sudo systemctl restart frpc.service")
print("frpc service is restarted.")
ip = gethostbyname(url)
ExecCommandIndex += 1
else:
print("Domain frp.imzlp.com DNS IP is", ip)
time.sleep(10)

Similarly, I added it to the systemctl for startup:

1
2
3
4
5
6
7
8
9
10
11
# /etc/systemd/system/syncfrpcdns.service
[Unit]
Description=Sync frpc Domain Server daemon

[Service]
Type=idle
ExecStartPre=/bin/sleep 10
ExecStart=/usr/bin/python3.4 /etc/frp/SyncFrpServerHost.py

[Install]
WantedBy=multi-user.target

It is also set to start on boot:

1
$ sudo systemctl enable syncfrpcdns.service

The above script can only run on Linux; I also wrote a similar one that can run on Windows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# SyncFrpServerHost.py
# Usable on Windows
import socket
import time
import os
import subprocess
def GetHotsByName(url="frp.imzlp.com"):
ip=socket.gethostbyname(url)
return ip
def FrpcIsRuning():
findTaskProcess=os.popen('tasklist|find "frpc.exe"')
findTaskOut=findTaskProcess.read()
return findTaskOut.find("frpc.exe") != -1

def StartFrp(frpcDir,frpcIniDir,FrpcStartUpInfo):
frpProcess=subprocess.Popen([frpcDir,"-c",frpcIniDir],stdout = subprocess.PIPE,startupinfo=FrpcStartUpInfo)
while not FrpcIsRuning():
print("Starting Frpc Process Again...")
frpProcess=subprocess.Popen([frpcDir,"-c",frpcIniDir],stdout = subprocess.PIPE,startupinfo=FrpcStartUpInfo)
print("Frpc Process is starting.")
return frpProcess

def main():
frpcDir="C:\\Program Files\\BuildPath\\UserTools\\frp\\frpc.exe"
frpcIniDir="C:\\Program Files\\BuildPath\\UserTools\\frp\\frpc.ini"
frpcDomainUrl="frp.imzlp.com"
UpdateSleepTime=10

# Hide the fcp window
FrpcStartUpInfo=subprocess.STARTUPINFO()
FrpcStartUpInfo.dwFlags = subprocess.CREATE_NEW_CONSOLE | subprocess.STARTF_USESHOWWINDOW
FrpcStartUpInfo.wShowWindow=subprocess.SW_HIDE

if FrpcIsRuning():
print("Restarting Frpc Process...")
os.system("taskkill /f /t /im frpc.exe")
frpProcess=StartFrp(frpcDir,frpcIniDir,FrpcStartUpInfo)
ip=GetHotsByName(frpcDomainUrl)
ExecCommandIndex=0
while True:
if not FrpcIsRuning():
frpProcess=StartFrp(frpcDir,frpcIniDir,FrpcStartUpInfo)
if(ip != GetHotsByName(frpcDomainUrl)):
frpProcess.kill()
print("Restarting frpc service.")
frpProcess=StartFrp(frpcDir,frpcIniDir,FrpcStartUpInfo)
print("frpc service is restarted.")
ip=GetHotsByName(frpcDomainUrl)
ExecCommandIndex += 1
else:
print("Domain frp.imzlp.com DNS IP is", ip)
time.sleep(UpdateSleepTime)

main()

Note: This script can act as the frpc startup program without needing to set frpc to start separately. It’s best to set login_fail_exit to false in frpc.ini.
You can create a simple VBS to hide the window when starting, or you can use pythonw:

1
2
3
DIM objShell
set objShell=wscript.createObject("wscript.shell")
iReturn=objShell.Run("python SyncFrpServerHost.py", 0, TRUE)

Place its shortcut in C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp.

This way, when I switch the server for frp.imzlp.me, the client will detect DNS changes and update the connection to the new server.

Conclusion

The most important thing during the operation is to check if the port is allowed through the firewall. If you cannot connect, it’s very likely that the port is blocked rather than a problem with the frp configuration.
For more ways to use frp, check here: frp/README.

Update

2018.11.10 16:19

  • Added the use of systemd to start frps on the server.
  • Revised some wording.
  • Added information about Alibaba Cloud security groups.
The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:使用frp进行内网穿透
Author:LIPENGZHA
Publish Date:2018/06/09 11:40
Update Date:2018/11/10 16:19
World Count:7.2k Words
Link:https://en.imzlp.com/posts/5050/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!