github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/network/debinterfaces/bridge.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package debinterfaces 5 6 import ( 7 "fmt" 8 "strings" 9 ) 10 11 type deviceNameSet map[string]bool 12 13 // The set of options which should be moved from an existing 'iface' 14 // stanza when turning it into a bridged device. 15 var bridgeOnlyOptions = []string{ 16 "address", 17 "gateway", 18 "netmask", 19 "dns-nameservers", 20 "dns-search", 21 "dns-sortlist", 22 } 23 24 func pruneOptions(options []string, names ...string) []string { 25 toPrune := map[string]bool{} 26 for _, v := range names { 27 toPrune[v] = true 28 } 29 result := make([]string, 0, len(options)) 30 for _, o := range options { 31 words := strings.Fields(o) 32 if len(words) >= 1 && !toPrune[words[0]] { 33 result = append(result, o) 34 } 35 } 36 return result 37 } 38 39 func pruneOptionsWithPrefix(options []string, prefix string) []string { 40 result := make([]string, 0, len(options)) 41 for _, o := range options { 42 words := strings.Fields(o) 43 if len(words) >= 1 && !strings.HasPrefix(words[0], prefix) { 44 result = append(result, o) 45 } 46 } 47 return result 48 } 49 50 func isLoopbackDevice(s *IfaceStanza) bool { 51 words := strings.Fields(s.Definition()[0]) 52 return len(words) >= 4 && words[3] == "loopback" 53 } 54 55 func newAutoStanza(deviceNames ...string) *AutoStanza { 56 return &AutoStanza{ 57 stanza: stanza{ 58 definition: fmt.Sprintf("auto %s", strings.Join(deviceNames, " ")), 59 }, 60 DeviceNames: deviceNames, 61 } 62 } 63 64 func isDefinitionBridgeable(iface *IfaceStanza) bool { 65 definitions := iface.definition 66 words := strings.Fields(definitions) 67 return len(words) >= 4 68 } 69 70 func turnManual(bridgeName string, iface IfaceStanza) *IfaceStanza { 71 if iface.IsAlias { 72 words := strings.Fields(iface.definition) 73 words[1] = bridgeName 74 iface.definition = strings.Join(words, " ") 75 return &iface 76 } 77 words := strings.Fields(iface.definition) 78 words[3] = "manual" 79 iface.definition = strings.Join(words, " ") 80 iface.Options = pruneOptions(iface.Options, bridgeOnlyOptions...) 81 return &iface 82 } 83 84 func bridgeInterface(bridgeName string, iface IfaceStanza) *IfaceStanza { 85 words := strings.Fields(iface.definition) 86 words[1] = bridgeName 87 iface.definition = strings.Join(words, " ") 88 iface.Options = pruneOptions(iface.Options, "mtu") 89 if iface.IsVLAN { 90 iface.Options = pruneOptions(iface.Options, "vlan_id", "vlan-raw-device") 91 } 92 if iface.HasBondOptions { 93 iface.Options = pruneOptionsWithPrefix(iface.Options, "bond-") 94 } 95 iface.Options = append(iface.Options, fmt.Sprintf("bridge_ports %s", iface.DeviceName)) 96 return &iface 97 } 98 99 func isBridgeable(iface *IfaceStanza) bool { 100 return isDefinitionBridgeable(iface) && 101 !isLoopbackDevice(iface) && 102 !iface.IsBridged && 103 !iface.HasBondMasterOption 104 } 105 106 // Bridge turns existing devices into bridged devices. 107 func Bridge(stanzas []Stanza, devices map[string]string) []Stanza { 108 result := make([]Stanza, 0) 109 autoStanzaSet := map[string]bool{} 110 manualInetSet := map[string]bool{} 111 manualInet6Set := map[string]bool{} 112 bridges := map[string]bool{} 113 ifacesToBridge := make([]IfaceStanza, 0) 114 devicesToBridge := deviceNameSet{} 115 116 for name := range devices { 117 devicesToBridge[name] = true 118 } 119 120 // We don't want to perturb the existing order, except for 121 // aliases which we do mutate in situ. All the bridged stanzas 122 // are added to the end of the original input. 123 124 for _, s := range stanzas { 125 switch v := s.(type) { 126 case IfaceStanza: 127 if devicesToBridge[v.DeviceName] && isBridgeable(&v) { 128 // we need to have only iface XXX inet manual 129 // TODO do we need to have separate manual for inet6 or is one sufficient? 130 if strings.Fields(v.definition)[2] == "inet" && !manualInetSet[v.DeviceName] { 131 result = append(result, *turnManual(devices[v.DeviceName], v)) 132 manualInetSet[v.DeviceName] = true 133 } 134 if strings.Fields(v.definition)[2] == "inet6" && !manualInet6Set[v.DeviceName] { 135 result = append(result, *turnManual(devices[v.DeviceName], v)) 136 manualInet6Set[v.DeviceName] = true 137 } 138 ifacesToBridge = append(ifacesToBridge, v) 139 } else { 140 result = append(result, s) 141 if v.IsBridged { 142 bridges[v.DeviceName] = true 143 } 144 } 145 case AutoStanza: 146 names := v.DeviceNames 147 for i, name := range names { 148 if isAlias(name) && devicesToBridge[name] { 149 names[i] = devices[name] 150 autoStanzaSet[names[i]] = true 151 } else { 152 autoStanzaSet[names[i]] = true 153 } 154 } 155 result = append(result, *newAutoStanza(names...)) 156 case SourceStanza: 157 v.Stanzas = Bridge(v.Stanzas, devices) 158 result = append(result, v) 159 case SourceDirectoryStanza: 160 v.Stanzas = Bridge(v.Stanzas, devices) 161 result = append(result, v) 162 default: 163 result = append(result, v) 164 } 165 } 166 167 for _, iface := range ifacesToBridge { 168 bridgeName := devices[iface.DeviceName] 169 // skip it if we already have a bridge of this name 170 if _, ok := bridges[bridgeName]; ok { 171 continue 172 } 173 174 // If there was a `auto $DEVICE` stanza make sure we 175 // create the complementary auto stanza for the bridge 176 // device. 177 if autoStanzaSet[iface.DeviceName] { 178 if !autoStanzaSet[bridgeName] { 179 autoStanzaSet[bridgeName] = true 180 result = append(result, *newAutoStanza(bridgeName)) 181 } 182 } 183 if isBridgeable(&iface) && !iface.IsAlias { 184 result = append(result, *bridgeInterface(bridgeName, iface)) 185 } 186 } 187 188 return result 189 }