k8s.io/kubernetes@v1.29.3/pkg/util/iptables/monitor_test.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2019 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package iptables 21 22 import ( 23 "context" 24 "fmt" 25 "io" 26 "sync" 27 "sync/atomic" 28 "testing" 29 "time" 30 31 "k8s.io/apimachinery/pkg/util/sets" 32 utilwait "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/utils/exec" 34 ) 35 36 // We can't use the normal FakeExec because we don't know precisely how many times the 37 // Monitor thread will do its checks, and we don't know precisely how its iptables calls 38 // will interleave with the main thread's. So we use our own fake Exec implementation that 39 // implements a minimal iptables interface. This will need updates as iptables.runner 40 // changes its use of Exec. 41 type monitorFakeExec struct { 42 sync.Mutex 43 44 tables map[string]sets.String 45 46 block bool 47 wasBlocked bool 48 } 49 50 func newMonitorFakeExec() *monitorFakeExec { 51 tables := make(map[string]sets.String) 52 tables["mangle"] = sets.NewString() 53 tables["filter"] = sets.NewString() 54 tables["nat"] = sets.NewString() 55 return &monitorFakeExec{tables: tables} 56 } 57 58 func (mfe *monitorFakeExec) blockIPTables(block bool) { 59 mfe.Lock() 60 defer mfe.Unlock() 61 62 mfe.block = block 63 } 64 65 func (mfe *monitorFakeExec) getWasBlocked() bool { 66 mfe.Lock() 67 defer mfe.Unlock() 68 69 wasBlocked := mfe.wasBlocked 70 mfe.wasBlocked = false 71 return wasBlocked 72 } 73 74 func (mfe *monitorFakeExec) Command(cmd string, args ...string) exec.Cmd { 75 return &monitorFakeCmd{mfe: mfe, cmd: cmd, args: args} 76 } 77 78 func (mfe *monitorFakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd { 79 return mfe.Command(cmd, args...) 80 } 81 82 func (mfe *monitorFakeExec) LookPath(file string) (string, error) { 83 return file, nil 84 } 85 86 type monitorFakeCmd struct { 87 mfe *monitorFakeExec 88 cmd string 89 args []string 90 } 91 92 func (mfc *monitorFakeCmd) CombinedOutput() ([]byte, error) { 93 if mfc.cmd == cmdIPTablesRestore { 94 // Only used for "iptables-restore --version", and the result doesn't matter 95 return []byte{}, nil 96 } else if mfc.cmd != cmdIPTables { 97 panic("bad command " + mfc.cmd) 98 } 99 100 if len(mfc.args) == 1 && mfc.args[0] == "--version" { 101 return []byte("iptables v1.6.2"), nil 102 } 103 104 if len(mfc.args) != 8 || mfc.args[0] != WaitString || mfc.args[1] != WaitSecondsValue || mfc.args[2] != WaitIntervalString || mfc.args[3] != WaitIntervalUsecondsValue || mfc.args[6] != "-t" { 105 panic(fmt.Sprintf("bad args %#v", mfc.args)) 106 } 107 op := operation(mfc.args[4]) 108 chainName := mfc.args[5] 109 tableName := mfc.args[7] 110 111 mfc.mfe.Lock() 112 defer mfc.mfe.Unlock() 113 114 table := mfc.mfe.tables[tableName] 115 if table == nil { 116 return []byte{}, fmt.Errorf("no such table %q", tableName) 117 } 118 119 // For ease-of-testing reasons, blockIPTables blocks create and list, but not delete 120 if mfc.mfe.block && op != opDeleteChain { 121 mfc.mfe.wasBlocked = true 122 return []byte{}, exec.CodeExitError{Code: 4, Err: fmt.Errorf("could not get xtables.lock, etc")} 123 } 124 125 switch op { 126 case opCreateChain: 127 if !table.Has(chainName) { 128 table.Insert(chainName) 129 } 130 return []byte{}, nil 131 case opListChain: 132 if table.Has(chainName) { 133 return []byte{}, nil 134 } 135 return []byte{}, fmt.Errorf("no such chain %q", chainName) 136 case opDeleteChain: 137 table.Delete(chainName) 138 return []byte{}, nil 139 default: 140 panic("should not be reached") 141 } 142 } 143 144 func (mfc *monitorFakeCmd) SetStdin(in io.Reader) { 145 // Used by getIPTablesRestoreVersionString(), can be ignored 146 } 147 148 func (mfc *monitorFakeCmd) Run() error { 149 panic("should not be reached") 150 } 151 func (mfc *monitorFakeCmd) Output() ([]byte, error) { 152 panic("should not be reached") 153 } 154 func (mfc *monitorFakeCmd) SetDir(dir string) { 155 panic("should not be reached") 156 } 157 func (mfc *monitorFakeCmd) SetStdout(out io.Writer) { 158 panic("should not be reached") 159 } 160 func (mfc *monitorFakeCmd) SetStderr(out io.Writer) { 161 panic("should not be reached") 162 } 163 func (mfc *monitorFakeCmd) SetEnv(env []string) { 164 panic("should not be reached") 165 } 166 func (mfc *monitorFakeCmd) StdoutPipe() (io.ReadCloser, error) { 167 panic("should not be reached") 168 } 169 func (mfc *monitorFakeCmd) StderrPipe() (io.ReadCloser, error) { 170 panic("should not be reached") 171 } 172 func (mfc *monitorFakeCmd) Start() error { 173 panic("should not be reached") 174 } 175 func (mfc *monitorFakeCmd) Wait() error { 176 panic("should not be reached") 177 } 178 func (mfc *monitorFakeCmd) Stop() { 179 panic("should not be reached") 180 } 181 182 func TestIPTablesMonitor(t *testing.T) { 183 mfe := newMonitorFakeExec() 184 ipt := New(mfe, ProtocolIPv4) 185 186 var reloads uint32 187 stopCh := make(chan struct{}) 188 189 canary := Chain("MONITOR-TEST-CANARY") 190 tables := []Table{TableMangle, TableFilter, TableNAT} 191 go ipt.Monitor(canary, tables, func() { 192 if !ensureNoChains(mfe) { 193 t.Errorf("reload called while canaries still exist") 194 } 195 atomic.AddUint32(&reloads, 1) 196 }, 100*time.Millisecond, stopCh) 197 198 // Monitor should create canary chains quickly 199 if err := waitForChains(mfe, canary, tables); err != nil { 200 t.Errorf("failed to create iptables canaries: %v", err) 201 } 202 203 if err := waitForReloads(&reloads, 0); err != nil { 204 t.Errorf("got unexpected reloads: %v", err) 205 } 206 207 // If we delete all of the chains, it should reload 208 ipt.DeleteChain(TableMangle, canary) 209 ipt.DeleteChain(TableFilter, canary) 210 ipt.DeleteChain(TableNAT, canary) 211 212 if err := waitForReloads(&reloads, 1); err != nil { 213 t.Errorf("got unexpected number of reloads after flush: %v", err) 214 } 215 if err := waitForChains(mfe, canary, tables); err != nil { 216 t.Errorf("failed to create iptables canaries: %v", err) 217 } 218 219 // If we delete two chains, it should not reload yet 220 ipt.DeleteChain(TableMangle, canary) 221 ipt.DeleteChain(TableFilter, canary) 222 223 if err := waitForNoReload(&reloads, 1); err != nil { 224 t.Errorf("got unexpected number of reloads after partial flush: %v", err) 225 } 226 227 // Now ensure that "iptables -L" will get an error about the xtables.lock, and 228 // delete the last chain. The monitor should not reload, because it can't actually 229 // tell if the chain was deleted or not. 230 mfe.blockIPTables(true) 231 ipt.DeleteChain(TableNAT, canary) 232 if err := waitForBlocked(mfe); err != nil { 233 t.Errorf("failed waiting for monitor to be blocked from monitoring: %v", err) 234 } 235 236 // After unblocking the monitor, it should now reload 237 mfe.blockIPTables(false) 238 239 if err := waitForReloads(&reloads, 2); err != nil { 240 t.Errorf("got unexpected number of reloads after slow flush: %v", err) 241 } 242 if err := waitForChains(mfe, canary, tables); err != nil { 243 t.Errorf("failed to create iptables canaries: %v", err) 244 } 245 246 // If we close the stop channel, it should stop running 247 close(stopCh) 248 249 if err := waitForNoReload(&reloads, 2); err != nil { 250 t.Errorf("got unexpected number of reloads after stop: %v", err) 251 } 252 if !ensureNoChains(mfe) { 253 t.Errorf("canaries still exist after stopping monitor") 254 } 255 256 // If we create a new monitor while the iptables lock is held, it will 257 // retry creating canaries until it succeeds 258 259 stopCh = make(chan struct{}) 260 _ = mfe.getWasBlocked() 261 mfe.blockIPTables(true) 262 go ipt.Monitor(canary, tables, func() { 263 if !ensureNoChains(mfe) { 264 t.Errorf("reload called while canaries still exist") 265 } 266 atomic.AddUint32(&reloads, 1) 267 }, 100*time.Millisecond, stopCh) 268 269 // Monitor should not have created canaries yet 270 if !ensureNoChains(mfe) { 271 t.Errorf("canary created while iptables blocked") 272 } 273 274 if err := waitForBlocked(mfe); err != nil { 275 t.Errorf("failed waiting for monitor to fail creating canaries: %v", err) 276 } 277 278 mfe.blockIPTables(false) 279 if err := waitForChains(mfe, canary, tables); err != nil { 280 t.Errorf("failed to create iptables canaries: %v", err) 281 } 282 283 close(stopCh) 284 } 285 286 func waitForChains(mfe *monitorFakeExec, canary Chain, tables []Table) error { 287 return utilwait.PollImmediate(100*time.Millisecond, time.Second, func() (bool, error) { 288 mfe.Lock() 289 defer mfe.Unlock() 290 291 for _, table := range tables { 292 if !mfe.tables[string(table)].Has(string(canary)) { 293 return false, nil 294 } 295 } 296 return true, nil 297 }) 298 } 299 300 func ensureNoChains(mfe *monitorFakeExec) bool { 301 mfe.Lock() 302 defer mfe.Unlock() 303 return mfe.tables["mangle"].Len() == 0 && 304 mfe.tables["filter"].Len() == 0 && 305 mfe.tables["nat"].Len() == 0 306 } 307 308 func waitForReloads(reloads *uint32, expected uint32) error { 309 if atomic.LoadUint32(reloads) < expected { 310 utilwait.PollImmediate(100*time.Millisecond, time.Second, func() (bool, error) { 311 return atomic.LoadUint32(reloads) >= expected, nil 312 }) 313 } 314 got := atomic.LoadUint32(reloads) 315 if got != expected { 316 return fmt.Errorf("expected %d, got %d", expected, got) 317 } 318 return nil 319 } 320 321 func waitForNoReload(reloads *uint32, expected uint32) error { 322 utilwait.PollImmediate(50*time.Millisecond, 250*time.Millisecond, func() (bool, error) { 323 return atomic.LoadUint32(reloads) > expected, nil 324 }) 325 326 got := atomic.LoadUint32(reloads) 327 if got != expected { 328 return fmt.Errorf("expected %d, got %d", expected, got) 329 } 330 return nil 331 } 332 333 func waitForBlocked(mfe *monitorFakeExec) error { 334 return utilwait.PollImmediate(100*time.Millisecond, time.Second, func() (bool, error) { 335 blocked := mfe.getWasBlocked() 336 return blocked, nil 337 }) 338 }