Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 端口转发功能 #5439

Merged
merged 2 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions backend/app/api/v1/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,29 @@ func (b *BaseApi) OperatePortRule(c *gin.Context) {
helper.SuccessWithData(c, nil)
}

// OperateForwardRule
// @Tags Firewall
// @Summary Create group
// @Description 更新防火墙端口转发规则
// @Accept json
// @Param request body dto.ForwardRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/forward [post]
// @x-panel-log {"bodyKeys":["source_port"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则 [source_port]","formatEN":"update port forward rules [source_port]"}
func (b *BaseApi) OperateForwardRule(c *gin.Context) {
var req dto.ForwardRuleOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}

if err := firewallService.OperateForwardRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

// @Tags Firewall
// @Summary Create group
// @Description 创建防火墙 IP 规则
Expand Down
11 changes: 11 additions & 0 deletions backend/app/dto/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ type PortRuleOperate struct {
Description string `json:"description"`
}

type ForwardRuleOperate struct {
Rules []struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Num string `json:"num"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
Port string `json:"port" validate:"required"`
TargetIP string `json:"targetIP"`
TargetPort string `json:"targetPort" validate:"required"`
} `json:"rules"`
}

type UpdateFirewallDescription struct {
Type string `json:"type"`
Address string `json:"address"`
Expand Down
9 changes: 9 additions & 0 deletions backend/app/model/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ type Firewall struct {
Strategy string `gorm:"type:varchar(64);not null" json:"strategy"`
Description string `gorm:"type:varchar(64);not null" json:"description"`
}

type Forward struct {
BaseModel

Protocol string `gorm:"type:varchar(64);not null" json:"protocol"`
Port string `gorm:"type:varchar(64);not null" json:"port"`
TargetIP string `gorm:"type:varchar(64);not null" json:"targetIP"`
TargetPort string `gorm:"type:varchar(64);not null" json:"targetPort"`
}
80 changes: 54 additions & 26 deletions backend/app/service/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package service
import (
"fmt"
"os"
"sort"
"strconv"
"strings"
"sync"
Expand All @@ -28,6 +29,7 @@ type IFirewallService interface {
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
OperateFirewall(operation string) error
OperatePortRule(req dto.PortRuleOperate, reload bool) error
OperateForwardRule(req dto.ForwardRuleOperate) error
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
UpdatePortRule(req dto.PortRuleUpdate) error
UpdateAddrRule(req dto.AddrRuleUpdate) error
Expand Down Expand Up @@ -78,42 +80,36 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
if err != nil {
return 0, nil, err
}
if req.Type == "port" {
ports, err := client.ListPort()
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, port := range ports {
if strings.Contains(port.Port, req.Info) {
datas = append(datas, port)
}

var rules []fireClient.FireInfo
switch req.Type {
case "port":
rules, err = client.ListPort()
case "forward":
rules, err = client.ListForward()
case "address":
rules, err = client.ListAddress()
}
if err != nil {
return 0, nil, err
}

if len(req.Info) != 0 {
for _, addr := range rules {
if strings.Contains(addr.Address, req.Info) {
datas = append(datas, addr)
}
} else {
datas = ports
}
} else {
addrs, err := client.ListAddress()
if err != nil {
return 0, nil, err
}
if len(req.Info) != 0 {
for _, addr := range addrs {
if strings.Contains(addr.Address, req.Info) {
datas = append(datas, addr)
}
}
} else {
datas = addrs
}
datas = rules
}

if req.Type == "port" {
apps := u.loadPortByApp()
for i := 0; i < len(datas); i++ {
datas[i].UsedStatus = checkPortUsed(datas[i].Port, datas[i].Protocol, apps)
}
}

var datasFilterStatus []fireClient.FireInfo
if len(req.Status) != 0 {
for _, data := range datas {
Expand All @@ -127,6 +123,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
} else {
datasFilterStatus = datas
}

var datasFilterStrategy []fireClient.FireInfo
if len(req.Strategy) != 0 {
for _, data := range datasFilterStatus {
Expand Down Expand Up @@ -300,6 +297,37 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
return nil
}

func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error {
client, err := firewall.NewFirewallClient()
endymx marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

sort.SliceStable(req.Rules, func(i, j int) bool {
n1, _ := strconv.Atoi(req.Rules[i].Num)
n2, _ := strconv.Atoi(req.Rules[j].Num)
return n1 > n2
})

for _, r := range req.Rules {
for _, p := range strings.Split(r.Protocol, "/") {
if r.TargetIP == "" {
r.TargetIP = "127.0.0.1"
}
if err = client.PortForward(fireClient.Forward{
Num: r.Num,
Protocol: p,
Port: r.Port,
TargetIP: r.TargetIP,
TargetPort: r.TargetPort,
}, r.Operation); err != nil {
return err
}
}
}
return nil
}

func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error {
client, err := firewall.NewFirewallClient()
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions backend/init/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/firewall"
"path"

"github.com/1Panel-dev/1Panel/backend/constant"
Expand Down Expand Up @@ -31,6 +32,10 @@ func Init() {
}

_ = docker.CreateDefaultDockerNetwork()

if f, err := firewall.NewFirewallClient(); err == nil {
_ = f.EnableForward()
}
}

func createDir(fileOp files.FileOp, dirPath string) {
Expand Down
1 change: 1 addition & 0 deletions backend/init/migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func Init() {
migrations.AddFtp,
migrations.AddProxy,
migrations.AddCronJobColumn,
migrations.AddForward,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
Expand Down
10 changes: 10 additions & 0 deletions backend/init/migration/migrations/v_1_10.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ var AddProxy = &gormigrate.Migration{
},
}

var AddForward = &gormigrate.Migration{
ID: "202400611-add-forward",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Forward{}); err != nil {
return err
}
return nil
},
}

var AddCronJobColumn = &gormigrate.Migration{
ID: "20240524-add-cronjob-command",
Migrate: func(tx *gorm.DB) error {
Expand Down
1 change: 1 addition & 0 deletions backend/router/ro_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
hostRouter.POST("/firewall/port", baseApi.OperatePortRule)
hostRouter.POST("/firewall/forward", baseApi.OperateForwardRule)
hostRouter.POST("/firewall/ip", baseApi.OperateIPRule)
hostRouter.POST("/firewall/batch", baseApi.BatchOperateRule)
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
Expand Down
3 changes: 3 additions & 0 deletions backend/utils/firewall/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ type FirewallClient interface {
Version() (string, error)

ListPort() ([]client.FireInfo, error)
ListForward() ([]client.FireInfo, error)
ListAddress() ([]client.FireInfo, error)

Port(port client.FireInfo, operation string) error
RichRules(rule client.FireInfo, operation string) error
PortForward(info client.Forward, operation string) error

EnableForward() error
}

func NewFirewallClient() (FirewallClient, error) {
Expand Down
51 changes: 48 additions & 3 deletions backend/utils/firewall/client/firewalld.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package client

import (
"fmt"
"regexp"
"strings"
"sync"

Expand All @@ -10,6 +11,8 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
)

var ForwardListRegex = regexp.MustCompile(`^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$`)

type Firewall struct{}

func NewFirewalld() (*Firewall, error) {
Expand Down Expand Up @@ -115,6 +118,29 @@ func (f *Firewall) ListPort() ([]FireInfo, error) {
return datas, nil
}

func (f *Firewall) ListForward() ([]FireInfo, error) {
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-forward-ports")
if err != nil {
return nil, err
}
var datas []FireInfo
for _, line := range strings.Split(stdout, "\n") {
line = strings.TrimFunc(line, func(r rune) bool {
return r <= 32
})
if ForwardListRegex.MatchString(line) {
match := ForwardListRegex.FindStringSubmatch(line)
datas = append(datas, FireInfo{
Port: match[1],
Protocol: match[2],
TargetIP: match[4],
TargetPort: match[3],
})
}
}
return datas, nil
}

func (f *Firewall) ListAddress() ([]FireInfo, error) {
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
if err != nil {
Expand Down Expand Up @@ -175,15 +201,18 @@ func (f *Firewall) RichRules(rule FireInfo, operation string) error {
}

func (f *Firewall) PortForward(info Forward, operation string) error {
ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target)
if len(info.Address) != 0 {
ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target)
ruleStr := fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetPort)
if info.TargetIP != "" && info.TargetIP != "127.0.0.1" && info.TargetIP != "localhost" {
ruleStr = fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort)
}

stdout, err := cmd.Exec(ruleStr)
if err != nil {
return fmt.Errorf("%s port forward failed, err: %s", operation, stdout)
}
if err = f.Reload(); err != nil {
return err
}
return nil
}

Expand All @@ -208,3 +237,19 @@ func (f *Firewall) loadInfo(line string) FireInfo {
}
return itemRule
}

func (f *Firewall) EnableForward() error {
stdout, err := cmd.Exec("firewall-cmd --zone=public --query-masquerade")
if err != nil {
if strings.HasSuffix(strings.TrimSpace(stdout), "no") {
stdout, err = cmd.Exec("firewall-cmd --zone=public --add-masquerade --permanent")
if err != nil {
return fmt.Errorf("%s: %s", err, stdout)
}
return f.Reload()
}
return fmt.Errorf("%s: %s", err, stdout)
}

return nil
}
24 changes: 20 additions & 4 deletions backend/utils/firewall/client/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,29 @@ type FireInfo struct {
Protocol string `json:"protocol"` // tcp udp tcp/udp
Strategy string `json:"strategy"` // accept drop

Num string `json:"num"`
TargetIP string `json:"targetIP"`
TargetPort string `json:"targetPort"`

UsedStatus string `json:"usedStatus"`
Description string `json:"description"`
}

type Forward struct {
Protocol string `json:"protocol"`
Address string `json:"address"`
Port string `json:"port"`
Target string `json:"target"`
Num string `json:"num"`
Protocol string `json:"protocol"`
Port string `json:"port"`
TargetIP string `json:"targetIP"`
TargetPort string `json:"targetPort"`
}

type IptablesNatInfo struct {
Num string `json:"num"`
Target string `json:"target"`
Protocol string `json:"protocol"`
Opt string `json:"opt"`
Source string `json:"source"`
Destination string `json:"destination"`
SrcPort string `json:"srcPort"`
DestPort string `json:"destPort"`
}
Loading