How to Configure Port Range Mapping Using Go Template Syntax in frp
frp supports port range mapping through Go template syntax by exposing parseNumberRange and parseNumberRangePair functions that expand range strings like "6000-6006,6007" into multiple proxy definitions at configuration load time.
The fatedier/frp project leverages Go's text/template engine to enable dynamic configuration generation. By using Go template syntax in your frp configuration files, you can configure port range mapping to automatically generate multiple proxy entries from compact range specifications, eliminating manual duplication for large-scale port forwarding setups.
Understanding frp's Template Functions
frp registers two custom helper functions into the template's FuncMap during the configuration loading process. These functions are defined in pkg/config/template.go and injected via pkg/config/load.go (lines 82-86).
parseNumberRange
The parseNumberRange function expands a single range string into a slice of integers. It accepts strings like "6000-6006,6007" and returns a slice of int64 values.
parseNumberRangePair
The parseNumberRangePair function is the primary tool for port range mapping. It takes two range strings and returns a slice of NumberPair structs, where each struct contains First and Second fields representing the local and remote ports respectively.
type NumberPair struct {
First int64
Second int64
}
The function ensures both ranges have identical lengths, returning an error if they mismatch. It relies on the underlying util.ParseRangeNumbers utility to handle the actual parsing logic.
Configuring Port Range Mappings
To configure port range mapping, you write a TOML template that uses Go template actions ({{ }}) to call the helper functions and iterate over the results.
Basic Port Range Syntax
The following example maps local ports 6000-6006 and 6007 to identical remote ports:
{{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }}
[[proxies]]
name = "tcp-{{ $v.First }}"
type = "tcp"
localPort = {{ $v.First }}
remotePort = {{ $v.Second }}
{{- end }}
When frp loads this configuration, it renders eight distinct [[proxies]] blocks, one for each port in the expanded range.
Different Local and Remote Ranges
You can map consecutive local ports to a different set of consecutive remote ports:
serverAddr = "example.com"
serverPort = 7000
# Map local ports 2000-2004 to remote ports 3000-3004
{{- range $_, $p := parseNumberRangePair "2000-2004" "3000-3004" }}
[[proxies]]
name = "tcp-{{ $p.First }}"
type = "tcp"
localPort = {{ $p.First }}
remotePort = {{ $p.Second }}
{{- end }}
This generates five proxy entries, mapping local port 2000 to remote 3000, 2001 to 3001, and so on.
How the Template Rendering Works
The configuration loading process in pkg/config/load.go orchestrates the template execution through the LoadConfigureFromFile function.
Template Initialization
When loading a configuration file, frp calls RenderWithTemplate, which creates a new template instance and registers the custom functions:
tmpl, err := template.New("frp").Funcs(template.FuncMap{
"parseNumberRange": parseNumberRange,
"parseNumberRangePair": parseNumberRangePair,
}).Parse(string(in))
Execution Flow
- File Reading: The raw configuration file is read as a byte slice.
- Template Execution: The template engine processes the file with access to environment variables and the custom
FuncMap. - Range Expansion: Calls to
parseNumberRangePairexpand into slices ofNumberPairstructs. - Proxy Generation: The
rangeaction iterates over these slices, emitting multiple[[proxies]]sections. - Parsing: The rendered output is parsed as standard TOML configuration.
This workflow enables declarative, compact definitions of large port mappings without manual duplication.
Summary
- frp uses Go's
text/templateengine to preprocess configuration files before parsing them as TOML. - The
parseNumberRangeandparseNumberRangePairfunctions, defined inpkg/config/template.go, enable expansion of port range strings into individual proxy definitions. parseNumberRangePairensures that local and remote port ranges have identical lengths, returningNumberPairstructs withFirstandSecondfields.- Configuration files use
{{- range }}actions to iterate over expanded ranges and generate multiple[[proxies]]blocks automatically. - The template functions are registered in
pkg/config/load.goand used extensively in frp's E2E test suite, such as intest/e2e/v1/basic/config.go.
Frequently Asked Questions
What is the syntax for port ranges in frp templates?
Port ranges use hyphenated notation for continuous ranges and commas for discrete values. For example, "6000-6006,6007" represents ports 6000 through 6006 plus port 6007. You pass these strings to parseNumberRange or parseNumberRangePair inside your template's range action.
Can I map different port ranges for local and remote ports?
Yes. Use parseNumberRangePair with two different range strings as arguments. The function expands both ranges and pairs them by position, ensuring they have the same length. For example, parseNumberRangePair "2000-2004" "3000-3004" creates pairs mapping local 2000 to remote 3000, 2001 to 3001, and so on.
What happens if the local and remote port ranges have different lengths?
The parseNumberRangePair function validates that both ranges produce the same number of ports. If the lengths differ, it returns an error during template execution, preventing the configuration from loading. This ensures every local port has a corresponding remote port and prevents misconfigurations.
Where are the template functions defined in the frp source code?
The helper functions are implemented in pkg/config/template.go, specifically parseNumberRange and parseNumberRangePair. They are registered into the template's FuncMap in pkg/config/load.go (around lines 82-86) when the configuration loader initializes the template engine.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →