1 package iptables
2
3 import (
4 "bytes"
5 "context"
6 "embed"
7 "io/fs"
8 "os"
9 "path/filepath"
10 "sort"
11 "text/template"
12
13 "github.com/spf13/afero"
14
15 v1 "k8s.io/api/core/v1"
16
17 k8snet "edge-infra.dev/pkg/k8s/net/calico"
18 "edge-infra.dev/pkg/k8s/runtime/controller/reconcile"
19 v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
20 "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config"
21 "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/networking/netplan"
22 network "edge-infra.dev/pkg/sds/ien/network/info"
23 nodemeta "edge-infra.dev/pkg/sds/ien/node"
24 )
25
26 var (
27
28 targetRules embed.FS
29 fwDirectory = "/etc/ien-fw/ipv4/dynamic"
30 )
31
32 type Plugin struct {
33 config config.Config
34 ienode *v1ien.IENode
35 }
36
37 type renderingParameters struct {
38 IsGateway bool
39 DefaultLinkName string
40 TunnelNetwork string
41 ServiceNetwork string
42 ClusterNetwork string
43 LocalNodeIP string
44 GatewayNodeIP string
45 OtherNodeIPs []string
46 KubeVip string
47 }
48
49 func (p Plugin) Reconcile(ctx context.Context, ienode *v1ien.IENode, cfg config.Config) (reconcile.Result, error) {
50 p.ienode = ienode
51 p.config = cfg
52 renderingParameters, err := p.compileRenderingParameters(ctx)
53 if err != nil {
54 return reconcile.ResultRequeue, err
55 }
56 if err := p.renderRules(renderingParameters); err != nil {
57 return reconcile.ResultRequeue, err
58 }
59 return reconcile.ResultSuccess, nil
60 }
61
62 func (p Plugin) renderRules(params *renderingParameters) error {
63 return fs.WalkDir(targetRules, "rules", func(path string, dir os.DirEntry, err error) error {
64 if dir.IsDir() || err != nil {
65 return err
66 }
67 template, err := template.ParseFS(targetRules, path)
68 if err != nil {
69 return err
70 }
71
72 basename := filepath.Base(path)
73 fwPath := filepath.Join(fwDirectory, basename)
74
75 var output bytes.Buffer
76 if err = template.ExecuteTemplate(&output, basename, params); err != nil {
77 return err
78 }
79
80 if equal, err := p.isFileContentsEqual(output.Bytes(), fwPath); err != nil || equal {
81 return err
82 }
83
84 return afero.WriteFile(p.config.Fs(), fwPath, output.Bytes(), 0644)
85 })
86 }
87
88 func (p Plugin) compileRenderingParameters(ctx context.Context) (*renderingParameters, error) {
89 params := &renderingParameters{}
90 params.IsGateway = p.ienode.Spec.IsGatewayNode()
91 tunnelNet, err := netplan.GetTunnelSubnet(ctx, p.config.GetClient())
92 if err != nil {
93 return nil, err
94 }
95
96 params.TunnelNetwork = tunnelNet.String()
97 params.KubeVip = p.ienode.Spec.KubeVip
98
99 if err := p.compileNetworkServiceParameters(ctx, params); err != nil {
100 return nil, err
101 }
102 if err := p.compileNodeParameters(ctx, params); err != nil {
103 return nil, err
104 }
105 return params, nil
106 }
107
108 func (p Plugin) compileNetworkServiceParameters(ctx context.Context, params *renderingParameters) error {
109 info := &network.Info{}
110 info, err := info.FromClient(ctx, p.config.GetClient())
111 if err != nil {
112 return err
113 }
114 params.ClusterNetwork = info.PodSubnet
115 params.ServiceNetwork = info.ServiceSubnet
116 return nil
117 }
118
119
120 func (p Plugin) compileNodeParameters(ctx context.Context, params *renderingParameters) error {
121 nodes, err := p.getNodes(ctx)
122 if err != nil {
123 return err
124 }
125
126 for _, node := range nodes {
127 node := node
128 ip, err := k8snet.ParseNodeIPs(node)
129 if err != nil {
130 return err
131 }
132 isGatewayNode, err := nodemeta.IsControlPlaneNode(&node)
133 if err != nil {
134 return err
135 }
136 if isGatewayNode {
137 params.GatewayNodeIP = ip[0].String()
138 }
139 if node.ObjectMeta.Name == p.ienode.ObjectMeta.Name {
140 params.LocalNodeIP = ip[0].String()
141 continue
142 }
143 params.OtherNodeIPs = append(params.OtherNodeIPs, ip[0].String())
144 }
145
146 address, err := p.ienode.Spec.DefaultMacAddress()
147 if err != nil {
148 return err
149 }
150
151 defaultLink, err := p.config.GetInterfaceFromHardwareAddress(address)
152 if err != nil {
153 return err
154 }
155 params.DefaultLinkName = defaultLink.Attrs().Name
156 return nil
157 }
158
159 func (p Plugin) isFileContentsEqual(contents []byte, path string) (bool, error) {
160 exists, err := afero.Exists(p.config.Fs(), path)
161 if err != nil || !exists {
162 return false, err
163 }
164
165 oldContents, err := afero.ReadFile(p.config.Fs(), path)
166 if err != nil {
167 return false, err
168 }
169
170 return bytes.Equal(oldContents, contents), nil
171 }
172
173 func (p Plugin) getNodes(ctx context.Context) ([]v1.Node, error) {
174 nodeList := &v1.NodeList{}
175 if err := p.config.GetClient().List(ctx, nodeList); err != nil {
176 return nil, err
177 }
178
179 nodes := nodeList.Items
180 sortNodesByName(nodes)
181
182 return nodes, nil
183 }
184
185 func sortNodesByName(nodes []v1.Node) {
186 sort.Slice(nodes, func(i, j int) bool {
187 return nodes[i].GetName() < nodes[j].GetName()
188 })
189 }
190
View as plain text