mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-06 05:52:19 +08:00
first commit
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package lxc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"clicd/internal/config"
|
||||
)
|
||||
|
||||
// ApplyPortMappings applies iptables DNAT rules for a container's port mappings
|
||||
func (m *Manager) ApplyPortMappings(id int) error {
|
||||
c := config.FindContainer(id)
|
||||
if c == nil {
|
||||
return fmt.Errorf("container not found: %d", id)
|
||||
}
|
||||
if c.IP == "" {
|
||||
return fmt.Errorf("container has no IP")
|
||||
}
|
||||
tag := clicdTag(id)
|
||||
|
||||
EnsureForwardRules()
|
||||
m.CleanPortMappings(id)
|
||||
|
||||
for _, pm := range c.PortMappings {
|
||||
cmd := exec.Command("iptables",
|
||||
"-t", "nat",
|
||||
"-I", "PREROUTING", "1",
|
||||
"-p", pm.Protocol,
|
||||
"--dport", fmt.Sprintf("%d", pm.HostPort),
|
||||
"-j", "DNAT",
|
||||
"--to-destination", fmt.Sprintf("%s:%d", c.IP, pm.ContainerPort),
|
||||
"-m", "comment", "--comment", fmt.Sprintf("clicd-%s-%d", tag, pm.HostPort),
|
||||
)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to apply port mapping %d->%s:%d: %v, output: %s\n",
|
||||
pm.HostPort, c.IP, pm.ContainerPort, err, string(output))
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Port mapping: host:%d -> %s:%d\n", pm.HostPort, c.IP, pm.ContainerPort)
|
||||
}
|
||||
|
||||
if exec.Command("iptables", "-t", "nat", "-C", "POSTROUTING", "-s", "10.0.3.0/24", "-o", "eth+", "-j", "MASQUERADE").Run() != nil {
|
||||
exec.Command("iptables", "-t", "nat", "-I", "POSTROUTING", "1", "-s", "10.0.3.0/24", "-o", "eth+", "-j", "MASQUERADE").Run()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clicdTag(id int) string { return "c" + strconv.Itoa(id) }
|
||||
|
||||
// EnsureForwardRules makes sure iptables FORWARD chain allows LXC bridge traffic
|
||||
func EnsureForwardRules() {
|
||||
rules := [][]string{
|
||||
{"-A", "FORWARD", "-i", "lxcbr0", "-j", "ACCEPT"},
|
||||
{"-A", "FORWARD", "-o", "lxcbr0", "-j", "ACCEPT"},
|
||||
{"-A", "FORWARD", "-i", "lxcbr0", "-o", "lxcbr0", "-j", "ACCEPT"},
|
||||
}
|
||||
for _, args := range rules {
|
||||
checkArgs := append([]string{"-C", "FORWARD"}, args[2:]...)
|
||||
if exec.Command("iptables", checkArgs...).Run() != nil {
|
||||
exec.Command("iptables", args...).Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CleanPortMappings removes all iptables rules for a container
|
||||
func (m *Manager) CleanPortMappings(id int) error {
|
||||
tag := clicdTag(id)
|
||||
cmd := exec.Command("sh", "-c",
|
||||
fmt.Sprintf("iptables -t nat -L PREROUTING -n --line-numbers 2>/dev/null | grep 'clicd-%s' | awk '{print $1}' | sort -rn | while read num; do iptables -t nat -D PREROUTING $num; done", tag))
|
||||
cmd.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupDefaultPortMappings creates default port mappings
|
||||
func SetupDefaultPortMappings(sshPort int) []config.PortMapping {
|
||||
return []config.PortMapping{
|
||||
{ContainerPort: 22, HostPort: sshPort, Protocol: "tcp", Description: "SSH"},
|
||||
}
|
||||
}
|
||||
|
||||
// AddPortMapping adds a NAT rule to a container
|
||||
func (m *Manager) AddPortMapping(id int, pm config.PortMapping) ([]config.PortMapping, error) {
|
||||
c := config.FindContainer(id)
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("container not found: %d", id)
|
||||
}
|
||||
if c.PortMappingLimit > 0 && len(c.PortMappings) >= c.PortMappingLimit {
|
||||
return nil, fmt.Errorf("port mapping quota exceeded: %d/%d", len(c.PortMappings), c.PortMappingLimit)
|
||||
}
|
||||
normalized, err := normalizePortMapping(c, -1, pm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.PortMappings = append(c.PortMappings, normalized)
|
||||
if err := persistAndReloadMappings(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.PortMappings, nil
|
||||
}
|
||||
|
||||
// UpdatePortMapping updates an existing NAT rule
|
||||
func (m *Manager) UpdatePortMapping(id int, index int, pm config.PortMapping) ([]config.PortMapping, error) {
|
||||
c := config.FindContainer(id)
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("container not found: %d", id)
|
||||
}
|
||||
if index < 0 || index >= len(c.PortMappings) {
|
||||
return nil, fmt.Errorf("invalid port mapping index: %d", index)
|
||||
}
|
||||
normalized, err := normalizePortMapping(c, index, pm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.PortMappings[index] = normalized
|
||||
if err := persistAndReloadMappings(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.PortMappings, nil
|
||||
}
|
||||
|
||||
// DeletePortMapping removes a NAT rule
|
||||
func (m *Manager) DeletePortMapping(id int, index int) ([]config.PortMapping, error) {
|
||||
c := config.FindContainer(id)
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("container not found: %d", id)
|
||||
}
|
||||
if index < 0 || index >= len(c.PortMappings) {
|
||||
return nil, fmt.Errorf("invalid port mapping index: %d", index)
|
||||
}
|
||||
if c.PortMappings[index].Description == "SSH" {
|
||||
return nil, fmt.Errorf("SSH default mapping cannot be deleted")
|
||||
}
|
||||
c.PortMappings = append(c.PortMappings[:index], c.PortMappings[index+1:]...)
|
||||
if err := persistAndReloadMappings(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.PortMappings, nil
|
||||
}
|
||||
|
||||
func persistAndReloadMappings(m *Manager, c *config.Container) error {
|
||||
config.SaveConfig()
|
||||
if c.Status == "running" && c.IP != "" {
|
||||
return m.ApplyPortMappings(c.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizePortMapping(c *config.Container, skipIndex int, pm config.PortMapping) (config.PortMapping, error) {
|
||||
if pm.ContainerPort < 1 || pm.ContainerPort > 65535 {
|
||||
return pm, fmt.Errorf("container port must be 1-65535")
|
||||
}
|
||||
if pm.Protocol == "" {
|
||||
pm.Protocol = "tcp"
|
||||
}
|
||||
if pm.Description == "" {
|
||||
pm.Description = fmt.Sprintf("Port-%d", pm.ContainerPort)
|
||||
}
|
||||
if pm.HostPort <= 0 {
|
||||
pm.HostPort = pm.ContainerPort
|
||||
}
|
||||
for i, existing := range c.PortMappings {
|
||||
if i == skipIndex {
|
||||
continue
|
||||
}
|
||||
if existing.HostPort == pm.HostPort && existing.Protocol == pm.Protocol {
|
||||
return pm, fmt.Errorf("host port %d/%s already mapped", pm.HostPort, pm.Protocol)
|
||||
}
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
func allocateDefaultEqualPorts(c *config.Container, count int) []int {
|
||||
if count <= 0 {
|
||||
return nil
|
||||
}
|
||||
used := map[int]bool{}
|
||||
for _, pm := range c.PortMappings {
|
||||
used[pm.HostPort] = true
|
||||
used[pm.ContainerPort] = true
|
||||
}
|
||||
ports := make([]int, 0, count)
|
||||
next := 20000
|
||||
for len(ports) < count {
|
||||
if !used[next] {
|
||||
ports = append(ports, next)
|
||||
}
|
||||
next++
|
||||
if next > 65535 || len(ports) >= count {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ports
|
||||
}
|
||||
Reference in New Issue
Block a user