Intranet Penetration: Principles, NAT, and FRP Implementation
This article explains the concepts of public and private IP addresses, NAT techniques, and the principle of intranet penetration, then demonstrates how to set up and analyze the open‑source FRP framework, including configuration files and core Go code for both server and client components.
Introduction
The article introduces the concept of intranet penetration, a technique that allows developers to expose services running on a local machine to the public Internet without deploying them to a remote server. It uses tools such as Ngrok, Peanut Shell, and the open‑source FRP framework.
Public IP vs. Private IP
Public IP addresses are globally routable addresses provided by ISPs, while private IP addresses are assigned by local network gateways and are only reachable within the same LAN. Accessing a device outside its LAN requires a public IP.
NAT and Port Multiplexing (PAT)
NAT (Network Address Translation) enables multiple internal hosts to share a single public IP. Three NAT methods are described: static translation, dynamic translation, and Port Address Translation (PAT), the latter being the most widely used to alleviate IPv4 scarcity.
Intranet Penetration Principle
Intranet penetration works by establishing a tunnel between a public server and a client behind a private network. The public server forwards incoming requests to the client, effectively mapping a public IP and port to the private service.
Setting Up a Simple FRP Service
Configuration files for the FRP server (frps.ini) and client (frpc.ini) are provided. The server listens on a bind port and an optional HTTP port, while the client specifies the server address, ports, and the services to expose.
服务端设置(frps.ini): [common]
bind_port = 7000 //此处填写客户端监听的服务端端口号
vhost_http_port = 8080 //此处填写用户访问的端口号
客户端配置(frpc.ini):
[common]
server_addr = x.x.x.x //此处填写服务端 IP 地址
server_port = 7000 //此处填写服务端配置的bind_port
[web]
type = http //此处规定转发请求的协议类型
local_port = 80 //此处规定本地服务启动的地址
custom_domains = www.example.com //此处可以填写自定义域名(需要在 IP 地址下配置域名解析)FRP Core Code Analysis
Server Initialization (frps)
func runServer(cfg config.ServerCommonConf) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
if cfgFile != "" {
log.Info("frps uses config file: %s", cfgFile)
} else {
log.Info("frps uses command line arguments for config")
}
// !important 核心代码1
svr, err := server.NewService(cfg)
if err != nil {
return err
}
log.Info("frps started successfully")
// !important 核心代码2
svr.Run()
return
}Client Initialization (frpc)
for {
// !important 核心代码3
conn, session, err := svr.login()
if err != nil {
xl.Warn("login to server failed: %v", err)
if svr.cfg.LoginFailExit {
return err
}
util.RandomSleep(10*time.Second, 0.9, 1.1)
} else {
// login success
// !important 核心代码4
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
ctl.Run()
svr.ctlMu.Lock()
svr.ctl = ctl
svr.ctlMu.Unlock()
break
}
}Server‑to‑Client Work Connection (frps → frpc)
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error) {
xl := xlog.FromContextSafe(pxy.ctx)
for i := 0; i < pxy.poolCount+1; i++ {
// !important 核心代码5
if workConn, err = pxy.getWorkConnFn(); err != nil {
xl.Warn("failed to get work connection: %v", err)
return
}
xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String())
xl.Spawn().AppendPrefix(pxy.GetName())
workConn = frpNet.NewContextConn(pxy.ctx, workConn)
// ...
// !important 核心代码6
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
ProxyName: pxy.GetName(),
SrcAddr: srcAddr,
SrcPort: uint16(srcPort),
DstAddr: dstAddr,
DstPort: uint16(dstPort),
Error: "",
})
}
}Control Writer (frps)
func (ctl *Control) writer() {
xl := ctl.xl
defer func() {
if err := recover(); err != nil {
xl.Error("panic error: %v", err)
xl.Error(string(debug.Stack()))
}
}()
defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
if err != nil {
xl.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start()
return
}
for {
m, ok := <-ctl.sendCh
if !ok {
xl.Info("control writer is closing")
return
}
// !important 核心代码8
if err := msg.WriteMsg(encWriter, m); err != nil {
xl.Warn("write message to control connection error: %v", err)
return
}
}
}Control Reader (frpc)
func (ctl *Control) reader() {
xl := ctl.xl
defer func() {
if err := recover(); err != nil {
xl.Error("panic error: %v", err)
xl.Error(string(debug.Stack()))
}
}()
defer ctl.readerShutdown.Done()
defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
for {
m, err := msg.ReadMsg(encReader)
if err != nil {
if err == io.EOF {
xl.Debug("read from control connection EOF")
return
}
xl.Warn("read error: %v", err)
ctl.conn.Close()
return
}
ctl.readCh <- m
}
}Conclusion
The article demonstrates that understanding IP addressing, NAT, and PAT is essential for implementing intranet penetration. By configuring a public FRP server and a client, developers can expose local services securely. The detailed code analysis shows how FRP establishes and manages the tunnel between public and private networks.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.