ipset.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package ipset
  14. import (
  15. "bytes"
  16. "fmt"
  17. "net"
  18. "regexp"
  19. "strconv"
  20. "strings"
  21. "k8s.io/klog"
  22. utilexec "k8s.io/utils/exec"
  23. )
  24. // Interface is an injectable interface for running ipset commands. Implementations must be goroutine-safe.
  25. type Interface interface {
  26. // FlushSet deletes all entries from a named set.
  27. FlushSet(set string) error
  28. // DestroySet deletes a named set.
  29. DestroySet(set string) error
  30. // DestroyAllSets deletes all sets.
  31. DestroyAllSets() error
  32. // CreateSet creates a new set. It will ignore error when the set already exists if ignoreExistErr=true.
  33. CreateSet(set *IPSet, ignoreExistErr bool) error
  34. // AddEntry adds a new entry to the named set. It will ignore error when the entry already exists if ignoreExistErr=true.
  35. AddEntry(entry string, set *IPSet, ignoreExistErr bool) error
  36. // DelEntry deletes one entry from the named set
  37. DelEntry(entry string, set string) error
  38. // Test test if an entry exists in the named set
  39. TestEntry(entry string, set string) (bool, error)
  40. // ListEntries lists all the entries from a named set
  41. ListEntries(set string) ([]string, error)
  42. // ListSets list all set names from kernel
  43. ListSets() ([]string, error)
  44. // GetVersion returns the "X.Y" version string for ipset.
  45. GetVersion() (string, error)
  46. }
  47. // IPSetCmd represents the ipset util. We use ipset command for ipset execute.
  48. const IPSetCmd = "ipset"
  49. // EntryMemberPattern is the regular expression pattern of ipset member list.
  50. // The raw output of ipset command `ipset list {set}` is similar to,
  51. //Name: foobar
  52. //Type: hash:ip,port
  53. //Revision: 2
  54. //Header: family inet hashsize 1024 maxelem 65536
  55. //Size in memory: 16592
  56. //References: 0
  57. //Members:
  58. //192.168.1.2,tcp:8080
  59. //192.168.1.1,udp:53
  60. var EntryMemberPattern = "(?m)^(.*\n)*Members:\n"
  61. // VersionPattern is the regular expression pattern of ipset version string.
  62. // ipset version output is similar to "v6.10".
  63. var VersionPattern = "v[0-9]+\\.[0-9]+"
  64. // IPSet implements an Interface to a set.
  65. type IPSet struct {
  66. // Name is the set name.
  67. Name string
  68. // SetType specifies the ipset type.
  69. SetType Type
  70. // HashFamily specifies the protocol family of the IP addresses to be stored in the set.
  71. // The default is inet, i.e IPv4. If users want to use IPv6, they should specify inet6.
  72. HashFamily string
  73. // HashSize specifies the hash table size of ipset.
  74. HashSize int
  75. // MaxElem specifies the max element number of ipset.
  76. MaxElem int
  77. // PortRange specifies the port range of bitmap:port type ipset.
  78. PortRange string
  79. // comment message for ipset
  80. Comment string
  81. }
  82. // Validate checks if a given ipset is valid or not.
  83. func (set *IPSet) Validate() bool {
  84. // Check if protocol is valid for `HashIPPort`, `HashIPPortIP` and `HashIPPortNet` type set.
  85. if set.SetType == HashIPPort || set.SetType == HashIPPortIP || set.SetType == HashIPPortNet {
  86. if valid := validateHashFamily(set.HashFamily); !valid {
  87. return false
  88. }
  89. }
  90. // check set type
  91. if valid := validateIPSetType(set.SetType); !valid {
  92. return false
  93. }
  94. // check port range for bitmap type set
  95. if set.SetType == BitmapPort {
  96. if valid := validatePortRange(set.PortRange); !valid {
  97. return false
  98. }
  99. }
  100. // check hash size value of ipset
  101. if set.HashSize <= 0 {
  102. klog.Errorf("Invalid hashsize value %d, should be >0", set.HashSize)
  103. return false
  104. }
  105. // check max elem value of ipset
  106. if set.MaxElem <= 0 {
  107. klog.Errorf("Invalid maxelem value %d, should be >0", set.MaxElem)
  108. return false
  109. }
  110. return true
  111. }
  112. //setIPSetDefaults sets some IPSet fields if not present to their default values.
  113. func (set *IPSet) setIPSetDefaults() {
  114. // Setting default values if not present
  115. if set.HashSize == 0 {
  116. set.HashSize = 1024
  117. }
  118. if set.MaxElem == 0 {
  119. set.MaxElem = 65536
  120. }
  121. // Default protocol is IPv4
  122. if set.HashFamily == "" {
  123. set.HashFamily = ProtocolFamilyIPV4
  124. }
  125. // Default ipset type is "hash:ip,port"
  126. if len(set.SetType) == 0 {
  127. set.SetType = HashIPPort
  128. }
  129. if len(set.PortRange) == 0 {
  130. set.PortRange = DefaultPortRange
  131. }
  132. }
  133. // Entry represents a ipset entry.
  134. type Entry struct {
  135. // IP is the entry's IP. The IP address protocol corresponds to the HashFamily of IPSet.
  136. // All entries' IP addresses in the same ip set has same the protocol, IPv4 or IPv6.
  137. IP string
  138. // Port is the entry's Port.
  139. Port int
  140. // Protocol is the entry's Protocol. The protocols of entries in the same ip set are all
  141. // the same. The accepted protocols are TCP, UDP and SCTP.
  142. Protocol string
  143. // Net is the entry's IP network address. Network address with zero prefix size can NOT
  144. // be stored.
  145. Net string
  146. // IP2 is the entry's second IP. IP2 may not be empty for `hash:ip,port,ip` type ip set.
  147. IP2 string
  148. // SetType is the type of ipset where the entry exists.
  149. SetType Type
  150. }
  151. // Validate checks if a given ipset entry is valid or not. The set parameter is the ipset that entry belongs to.
  152. func (e *Entry) Validate(set *IPSet) bool {
  153. if e.Port < 0 {
  154. klog.Errorf("Entry %v port number %d should be >=0 for ipset %v", e, e.Port, set)
  155. return false
  156. }
  157. switch e.SetType {
  158. case HashIPPort:
  159. //check if IP and Protocol of Entry is valid.
  160. if valid := e.checkIPandProtocol(set); !valid {
  161. return false
  162. }
  163. case HashIPPortIP:
  164. //check if IP and Protocol of Entry is valid.
  165. if valid := e.checkIPandProtocol(set); !valid {
  166. return false
  167. }
  168. // IP2 can not be empty for `hash:ip,port,ip` type ip set
  169. if net.ParseIP(e.IP2) == nil {
  170. klog.Errorf("Error parsing entry %v second ip address %v for ipset %v", e, e.IP2, set)
  171. return false
  172. }
  173. case HashIPPortNet:
  174. //check if IP and Protocol of Entry is valid.
  175. if valid := e.checkIPandProtocol(set); !valid {
  176. return false
  177. }
  178. // Net can not be empty for `hash:ip,port,net` type ip set
  179. if _, ipNet, err := net.ParseCIDR(e.Net); ipNet == nil {
  180. klog.Errorf("Error parsing entry %v ip net %v for ipset %v, error: %v", e, e.Net, set, err)
  181. return false
  182. }
  183. case BitmapPort:
  184. // check if port number satisfies its ipset's requirement of port range
  185. if set == nil {
  186. klog.Errorf("Unable to reference ip set where the entry %v exists", e)
  187. return false
  188. }
  189. begin, end, err := parsePortRange(set.PortRange)
  190. if err != nil {
  191. klog.Errorf("Failed to parse set %v port range %s for ipset %v, error: %v", set, set.PortRange, set, err)
  192. return false
  193. }
  194. if e.Port < begin || e.Port > end {
  195. klog.Errorf("Entry %v port number %d is not in the port range %s of its ipset %v", e, e.Port, set.PortRange, set)
  196. return false
  197. }
  198. }
  199. return true
  200. }
  201. // String returns the string format for ipset entry.
  202. func (e *Entry) String() string {
  203. switch e.SetType {
  204. case HashIPPort:
  205. // Entry{192.168.1.1, udp, 53} -> 192.168.1.1,udp:53
  206. // Entry{192.168.1.2, tcp, 8080} -> 192.168.1.2,tcp:8080
  207. return fmt.Sprintf("%s,%s:%s", e.IP, e.Protocol, strconv.Itoa(e.Port))
  208. case HashIPPortIP:
  209. // Entry{192.168.1.1, udp, 53, 10.0.0.1} -> 192.168.1.1,udp:53,10.0.0.1
  210. // Entry{192.168.1.2, tcp, 8080, 192.168.1.2} -> 192.168.1.2,tcp:8080,192.168.1.2
  211. return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.IP2)
  212. case HashIPPortNet:
  213. // Entry{192.168.1.2, udp, 80, 10.0.1.0/24} -> 192.168.1.2,udp:80,10.0.1.0/24
  214. // Entry{192.168.2,25, tcp, 8080, 10.1.0.0/16} -> 192.168.2,25,tcp:8080,10.1.0.0/16
  215. return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.Net)
  216. case BitmapPort:
  217. // Entry{53} -> 53
  218. // Entry{8080} -> 8080
  219. return strconv.Itoa(e.Port)
  220. }
  221. return ""
  222. }
  223. // checkIPandProtocol checks if IP and Protocol of Entry is valid.
  224. func (e *Entry) checkIPandProtocol(set *IPSet) bool {
  225. // set default protocol to tcp if empty
  226. if len(e.Protocol) == 0 {
  227. e.Protocol = ProtocolTCP
  228. } else if !validateProtocol(e.Protocol) {
  229. return false
  230. }
  231. if net.ParseIP(e.IP) == nil {
  232. klog.Errorf("Error parsing entry %v ip address %v for ipset %v", e, e.IP, set)
  233. return false
  234. }
  235. return true
  236. }
  237. type runner struct {
  238. exec utilexec.Interface
  239. }
  240. // New returns a new Interface which will exec ipset.
  241. func New(exec utilexec.Interface) Interface {
  242. return &runner{
  243. exec: exec,
  244. }
  245. }
  246. // CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true.
  247. func (runner *runner) CreateSet(set *IPSet, ignoreExistErr bool) error {
  248. // sets some IPSet fields if not present to their default values.
  249. set.setIPSetDefaults()
  250. // Validate ipset before creating
  251. valid := set.Validate()
  252. if !valid {
  253. return fmt.Errorf("error creating ipset since it's invalid")
  254. }
  255. return runner.createSet(set, ignoreExistErr)
  256. }
  257. // If ignoreExistErr is set to true, then the -exist option of ipset will be specified, ipset ignores the error
  258. // otherwise raised when the same set (setname and create parameters are identical) already exists.
  259. func (runner *runner) createSet(set *IPSet, ignoreExistErr bool) error {
  260. args := []string{"create", set.Name, string(set.SetType)}
  261. if set.SetType == HashIPPortIP || set.SetType == HashIPPort || set.SetType == HashIPPortNet {
  262. args = append(args,
  263. "family", set.HashFamily,
  264. "hashsize", strconv.Itoa(set.HashSize),
  265. "maxelem", strconv.Itoa(set.MaxElem),
  266. )
  267. }
  268. if set.SetType == BitmapPort {
  269. args = append(args, "range", set.PortRange)
  270. }
  271. if ignoreExistErr {
  272. args = append(args, "-exist")
  273. }
  274. if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil {
  275. return fmt.Errorf("error creating ipset %s, error: %v", set.Name, err)
  276. }
  277. return nil
  278. }
  279. // AddEntry adds a new entry to the named set.
  280. // If the -exist option is specified, ipset ignores the error otherwise raised when
  281. // the same set (setname and create parameters are identical) already exists.
  282. func (runner *runner) AddEntry(entry string, set *IPSet, ignoreExistErr bool) error {
  283. args := []string{"add", set.Name, entry}
  284. if ignoreExistErr {
  285. args = append(args, "-exist")
  286. }
  287. if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil {
  288. return fmt.Errorf("error adding entry %s, error: %v", entry, err)
  289. }
  290. return nil
  291. }
  292. // DelEntry is used to delete the specified entry from the set.
  293. func (runner *runner) DelEntry(entry string, set string) error {
  294. if _, err := runner.exec.Command(IPSetCmd, "del", set, entry).CombinedOutput(); err != nil {
  295. return fmt.Errorf("error deleting entry %s: from set: %s, error: %v", entry, set, err)
  296. }
  297. return nil
  298. }
  299. // TestEntry is used to check whether the specified entry is in the set or not.
  300. func (runner *runner) TestEntry(entry string, set string) (bool, error) {
  301. if out, err := runner.exec.Command(IPSetCmd, "test", set, entry).CombinedOutput(); err == nil {
  302. reg, e := regexp.Compile("is NOT in set " + set)
  303. if e == nil && reg.MatchString(string(out)) {
  304. return false, nil
  305. } else if e == nil {
  306. return true, nil
  307. } else {
  308. return false, fmt.Errorf("error testing entry: %s, error: %v", entry, e)
  309. }
  310. } else {
  311. return false, fmt.Errorf("error testing entry %s: %v (%s)", entry, err, out)
  312. }
  313. }
  314. // FlushSet deletes all entries from a named set.
  315. func (runner *runner) FlushSet(set string) error {
  316. if _, err := runner.exec.Command(IPSetCmd, "flush", set).CombinedOutput(); err != nil {
  317. return fmt.Errorf("error flushing set: %s, error: %v", set, err)
  318. }
  319. return nil
  320. }
  321. // DestroySet is used to destroy a named set.
  322. func (runner *runner) DestroySet(set string) error {
  323. if out, err := runner.exec.Command(IPSetCmd, "destroy", set).CombinedOutput(); err != nil {
  324. return fmt.Errorf("error destroying set %s, error: %v(%s)", set, err, out)
  325. }
  326. return nil
  327. }
  328. // DestroyAllSets is used to destroy all sets.
  329. func (runner *runner) DestroyAllSets() error {
  330. if _, err := runner.exec.Command(IPSetCmd, "destroy").CombinedOutput(); err != nil {
  331. return fmt.Errorf("error destroying all sets, error: %v", err)
  332. }
  333. return nil
  334. }
  335. // ListSets list all set names from kernel
  336. func (runner *runner) ListSets() ([]string, error) {
  337. out, err := runner.exec.Command(IPSetCmd, "list", "-n").CombinedOutput()
  338. if err != nil {
  339. return nil, fmt.Errorf("error listing all sets, error: %v", err)
  340. }
  341. return strings.Split(string(out), "\n"), nil
  342. }
  343. // ListEntries lists all the entries from a named set.
  344. func (runner *runner) ListEntries(set string) ([]string, error) {
  345. if len(set) == 0 {
  346. return nil, fmt.Errorf("set name can't be nil")
  347. }
  348. out, err := runner.exec.Command(IPSetCmd, "list", set).CombinedOutput()
  349. if err != nil {
  350. return nil, fmt.Errorf("error listing set: %s, error: %v", set, err)
  351. }
  352. memberMatcher := regexp.MustCompile(EntryMemberPattern)
  353. list := memberMatcher.ReplaceAllString(string(out[:]), "")
  354. strs := strings.Split(list, "\n")
  355. results := make([]string, 0)
  356. for i := range strs {
  357. if len(strs[i]) > 0 {
  358. results = append(results, strs[i])
  359. }
  360. }
  361. return results, nil
  362. }
  363. // GetVersion returns the version string.
  364. func (runner *runner) GetVersion() (string, error) {
  365. return getIPSetVersionString(runner.exec)
  366. }
  367. // getIPSetVersionString runs "ipset --version" to get the version string
  368. // in the form of "X.Y", i.e "6.19"
  369. func getIPSetVersionString(exec utilexec.Interface) (string, error) {
  370. cmd := exec.Command(IPSetCmd, "--version")
  371. cmd.SetStdin(bytes.NewReader([]byte{}))
  372. bytes, err := cmd.CombinedOutput()
  373. if err != nil {
  374. return "", err
  375. }
  376. versionMatcher := regexp.MustCompile(VersionPattern)
  377. match := versionMatcher.FindStringSubmatch(string(bytes))
  378. if match == nil {
  379. return "", fmt.Errorf("no ipset version found in string: %s", bytes)
  380. }
  381. return match[0], nil
  382. }
  383. // checks if port range is valid. The begin port number is not necessarily less than
  384. // end port number - ipset util can accept it. It means both 1-100 and 100-1 are valid.
  385. func validatePortRange(portRange string) bool {
  386. strs := strings.Split(portRange, "-")
  387. if len(strs) != 2 {
  388. klog.Errorf("port range should be in the format of `a-b`")
  389. return false
  390. }
  391. for i := range strs {
  392. num, err := strconv.Atoi(strs[i])
  393. if err != nil {
  394. klog.Errorf("Failed to parse %s, error: %v", strs[i], err)
  395. return false
  396. }
  397. if num < 0 {
  398. klog.Errorf("port number %d should be >=0", num)
  399. return false
  400. }
  401. }
  402. return true
  403. }
  404. // checks if the given ipset type is valid.
  405. func validateIPSetType(set Type) bool {
  406. for _, valid := range ValidIPSetTypes {
  407. if set == valid {
  408. return true
  409. }
  410. }
  411. klog.Errorf("Currently supported ipset types are: %v, %s is not supported", ValidIPSetTypes, set)
  412. return false
  413. }
  414. // checks if given hash family is supported in ipset
  415. func validateHashFamily(family string) bool {
  416. if family == ProtocolFamilyIPV4 || family == ProtocolFamilyIPV6 {
  417. return true
  418. }
  419. klog.Errorf("Currently supported ip set hash families are: [%s, %s], %s is not supported", ProtocolFamilyIPV4, ProtocolFamilyIPV6, family)
  420. return false
  421. }
  422. // IsNotFoundError returns true if the error indicates "not found". It parses
  423. // the error string looking for known values, which is imperfect but works in
  424. // practice.
  425. func IsNotFoundError(err error) bool {
  426. es := err.Error()
  427. if strings.Contains(es, "does not exist") {
  428. // set with the same name already exists
  429. // xref: https://github.com/Olipro/ipset/blob/master/lib/errcode.c#L32-L33
  430. return true
  431. }
  432. if strings.Contains(es, "element is missing") {
  433. // entry is missing from the set
  434. // xref: https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1904
  435. // https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1925
  436. return true
  437. }
  438. return false
  439. }
  440. // checks if given protocol is supported in entry
  441. func validateProtocol(protocol string) bool {
  442. if protocol == ProtocolTCP || protocol == ProtocolUDP || protocol == ProtocolSCTP {
  443. return true
  444. }
  445. klog.Errorf("Invalid entry's protocol: %s, supported protocols are [%s, %s, %s]", protocol, ProtocolTCP, ProtocolUDP, ProtocolSCTP)
  446. return false
  447. }
  448. // parsePortRange parse the begin and end port from a raw string(format: a-b). beginPort <= endPort
  449. // in the return value.
  450. func parsePortRange(portRange string) (beginPort int, endPort int, err error) {
  451. if len(portRange) == 0 {
  452. portRange = DefaultPortRange
  453. }
  454. strs := strings.Split(portRange, "-")
  455. if len(strs) != 2 {
  456. // port number -1 indicates invalid
  457. return -1, -1, fmt.Errorf("port range should be in the format of `a-b`")
  458. }
  459. for i := range strs {
  460. num, err := strconv.Atoi(strs[i])
  461. if err != nil {
  462. // port number -1 indicates invalid
  463. return -1, -1, err
  464. }
  465. if num < 0 {
  466. // port number -1 indicates invalid
  467. return -1, -1, fmt.Errorf("port number %d should be >=0", num)
  468. }
  469. if i == 0 {
  470. beginPort = num
  471. continue
  472. }
  473. endPort = num
  474. // switch when first port number > second port number
  475. if beginPort > endPort {
  476. endPort = beginPort
  477. beginPort = num
  478. }
  479. }
  480. return beginPort, endPort, nil
  481. }
  482. var _ = Interface(&runner{})