netlink.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. // +build linux
  2. package ipvs
  3. import (
  4. "bytes"
  5. "encoding/binary"
  6. "fmt"
  7. "net"
  8. "os/exec"
  9. "strings"
  10. "sync"
  11. "sync/atomic"
  12. "syscall"
  13. "time"
  14. "unsafe"
  15. "github.com/sirupsen/logrus"
  16. "github.com/vishvananda/netlink/nl"
  17. "github.com/vishvananda/netns"
  18. )
  19. // For Quick Reference IPVS related netlink message is described at the end of this file.
  20. var (
  21. native = nl.NativeEndian()
  22. ipvsFamily int
  23. ipvsOnce sync.Once
  24. )
  25. type genlMsgHdr struct {
  26. cmd uint8
  27. version uint8
  28. reserved uint16
  29. }
  30. type ipvsFlags struct {
  31. flags uint32
  32. mask uint32
  33. }
  34. func deserializeGenlMsg(b []byte) (hdr *genlMsgHdr) {
  35. return (*genlMsgHdr)(unsafe.Pointer(&b[0:unsafe.Sizeof(*hdr)][0]))
  36. }
  37. func (hdr *genlMsgHdr) Serialize() []byte {
  38. return (*(*[unsafe.Sizeof(*hdr)]byte)(unsafe.Pointer(hdr)))[:]
  39. }
  40. func (hdr *genlMsgHdr) Len() int {
  41. return int(unsafe.Sizeof(*hdr))
  42. }
  43. func (f *ipvsFlags) Serialize() []byte {
  44. return (*(*[unsafe.Sizeof(*f)]byte)(unsafe.Pointer(f)))[:]
  45. }
  46. func (f *ipvsFlags) Len() int {
  47. return int(unsafe.Sizeof(*f))
  48. }
  49. func setup() {
  50. ipvsOnce.Do(func() {
  51. var err error
  52. if out, err := exec.Command("modprobe", "-va", "ip_vs").CombinedOutput(); err != nil {
  53. logrus.Warnf("Running modprobe ip_vs failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
  54. }
  55. ipvsFamily, err = getIPVSFamily()
  56. if err != nil {
  57. logrus.Error("Could not get ipvs family information from the kernel. It is possible that ipvs is not enabled in your kernel. Native loadbalancing will not work until this is fixed.")
  58. }
  59. })
  60. }
  61. func fillService(s *Service) nl.NetlinkRequestData {
  62. cmdAttr := nl.NewRtAttr(ipvsCmdAttrService, nil)
  63. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrAddressFamily, nl.Uint16Attr(s.AddressFamily))
  64. if s.FWMark != 0 {
  65. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrFWMark, nl.Uint32Attr(s.FWMark))
  66. } else {
  67. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrProtocol, nl.Uint16Attr(s.Protocol))
  68. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrAddress, rawIPData(s.Address))
  69. // Port needs to be in network byte order.
  70. portBuf := new(bytes.Buffer)
  71. binary.Write(portBuf, binary.BigEndian, s.Port)
  72. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrPort, portBuf.Bytes())
  73. }
  74. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrSchedName, nl.ZeroTerminated(s.SchedName))
  75. if s.PEName != "" {
  76. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrPEName, nl.ZeroTerminated(s.PEName))
  77. }
  78. f := &ipvsFlags{
  79. flags: s.Flags,
  80. mask: 0xFFFFFFFF,
  81. }
  82. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrFlags, f.Serialize())
  83. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrTimeout, nl.Uint32Attr(s.Timeout))
  84. nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrNetmask, nl.Uint32Attr(s.Netmask))
  85. return cmdAttr
  86. }
  87. func fillDestination(d *Destination) nl.NetlinkRequestData {
  88. cmdAttr := nl.NewRtAttr(ipvsCmdAttrDest, nil)
  89. nl.NewRtAttrChild(cmdAttr, ipvsDestAttrAddress, rawIPData(d.Address))
  90. // Port needs to be in network byte order.
  91. portBuf := new(bytes.Buffer)
  92. binary.Write(portBuf, binary.BigEndian, d.Port)
  93. nl.NewRtAttrChild(cmdAttr, ipvsDestAttrPort, portBuf.Bytes())
  94. nl.NewRtAttrChild(cmdAttr, ipvsDestAttrForwardingMethod, nl.Uint32Attr(d.ConnectionFlags&ConnectionFlagFwdMask))
  95. nl.NewRtAttrChild(cmdAttr, ipvsDestAttrWeight, nl.Uint32Attr(uint32(d.Weight)))
  96. nl.NewRtAttrChild(cmdAttr, ipvsDestAttrUpperThreshold, nl.Uint32Attr(d.UpperThreshold))
  97. nl.NewRtAttrChild(cmdAttr, ipvsDestAttrLowerThreshold, nl.Uint32Attr(d.LowerThreshold))
  98. return cmdAttr
  99. }
  100. func (i *Handle) doCmdwithResponse(s *Service, d *Destination, cmd uint8) ([][]byte, error) {
  101. req := newIPVSRequest(cmd)
  102. req.Seq = atomic.AddUint32(&i.seq, 1)
  103. if s == nil {
  104. req.Flags |= syscall.NLM_F_DUMP //Flag to dump all messages
  105. req.AddData(nl.NewRtAttr(ipvsCmdAttrService, nil)) //Add a dummy attribute
  106. } else {
  107. req.AddData(fillService(s))
  108. }
  109. if d == nil {
  110. if cmd == ipvsCmdGetDest {
  111. req.Flags |= syscall.NLM_F_DUMP
  112. }
  113. } else {
  114. req.AddData(fillDestination(d))
  115. }
  116. res, err := execute(i.sock, req, 0)
  117. if err != nil {
  118. return [][]byte{}, err
  119. }
  120. return res, nil
  121. }
  122. func (i *Handle) doCmd(s *Service, d *Destination, cmd uint8) error {
  123. _, err := i.doCmdwithResponse(s, d, cmd)
  124. return err
  125. }
  126. func getIPVSFamily() (int, error) {
  127. sock, err := nl.GetNetlinkSocketAt(netns.None(), netns.None(), syscall.NETLINK_GENERIC)
  128. if err != nil {
  129. return 0, err
  130. }
  131. defer sock.Close()
  132. req := newGenlRequest(genlCtrlID, genlCtrlCmdGetFamily)
  133. req.AddData(nl.NewRtAttr(genlCtrlAttrFamilyName, nl.ZeroTerminated("IPVS")))
  134. msgs, err := execute(sock, req, 0)
  135. if err != nil {
  136. return 0, err
  137. }
  138. for _, m := range msgs {
  139. hdr := deserializeGenlMsg(m)
  140. attrs, err := nl.ParseRouteAttr(m[hdr.Len():])
  141. if err != nil {
  142. return 0, err
  143. }
  144. for _, attr := range attrs {
  145. switch int(attr.Attr.Type) {
  146. case genlCtrlAttrFamilyID:
  147. return int(native.Uint16(attr.Value[0:2])), nil
  148. }
  149. }
  150. }
  151. return 0, fmt.Errorf("no family id in the netlink response")
  152. }
  153. func rawIPData(ip net.IP) []byte {
  154. family := nl.GetIPFamily(ip)
  155. if family == nl.FAMILY_V4 {
  156. return ip.To4()
  157. }
  158. return ip
  159. }
  160. func newIPVSRequest(cmd uint8) *nl.NetlinkRequest {
  161. return newGenlRequest(ipvsFamily, cmd)
  162. }
  163. func newGenlRequest(familyID int, cmd uint8) *nl.NetlinkRequest {
  164. req := nl.NewNetlinkRequest(familyID, syscall.NLM_F_ACK)
  165. req.AddData(&genlMsgHdr{cmd: cmd, version: 1})
  166. return req
  167. }
  168. func execute(s *nl.NetlinkSocket, req *nl.NetlinkRequest, resType uint16) ([][]byte, error) {
  169. if err := s.Send(req); err != nil {
  170. return nil, err
  171. }
  172. pid, err := s.GetPid()
  173. if err != nil {
  174. return nil, err
  175. }
  176. var res [][]byte
  177. done:
  178. for {
  179. msgs, err := s.Receive()
  180. if err != nil {
  181. if s.GetFd() == -1 {
  182. return nil, fmt.Errorf("Socket got closed on receive")
  183. }
  184. if err == syscall.EAGAIN {
  185. // timeout fired
  186. continue
  187. }
  188. return nil, err
  189. }
  190. for _, m := range msgs {
  191. if m.Header.Seq != req.Seq {
  192. continue
  193. }
  194. if m.Header.Pid != pid {
  195. return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
  196. }
  197. if m.Header.Type == syscall.NLMSG_DONE {
  198. break done
  199. }
  200. if m.Header.Type == syscall.NLMSG_ERROR {
  201. error := int32(native.Uint32(m.Data[0:4]))
  202. if error == 0 {
  203. break done
  204. }
  205. return nil, syscall.Errno(-error)
  206. }
  207. if resType != 0 && m.Header.Type != resType {
  208. continue
  209. }
  210. res = append(res, m.Data)
  211. if m.Header.Flags&syscall.NLM_F_MULTI == 0 {
  212. break done
  213. }
  214. }
  215. }
  216. return res, nil
  217. }
  218. func parseIP(ip []byte, family uint16) (net.IP, error) {
  219. var resIP net.IP
  220. switch family {
  221. case syscall.AF_INET:
  222. resIP = (net.IP)(ip[:4])
  223. case syscall.AF_INET6:
  224. resIP = (net.IP)(ip[:16])
  225. default:
  226. return nil, fmt.Errorf("parseIP Error ip=%v", ip)
  227. }
  228. return resIP, nil
  229. }
  230. // parseStats
  231. func assembleStats(msg []byte) (SvcStats, error) {
  232. var s SvcStats
  233. attrs, err := nl.ParseRouteAttr(msg)
  234. if err != nil {
  235. return s, err
  236. }
  237. for _, attr := range attrs {
  238. attrType := int(attr.Attr.Type)
  239. switch attrType {
  240. case ipvsSvcStatsConns:
  241. s.Connections = native.Uint32(attr.Value)
  242. case ipvsSvcStatsPktsIn:
  243. s.PacketsIn = native.Uint32(attr.Value)
  244. case ipvsSvcStatsPktsOut:
  245. s.PacketsOut = native.Uint32(attr.Value)
  246. case ipvsSvcStatsBytesIn:
  247. s.BytesIn = native.Uint64(attr.Value)
  248. case ipvsSvcStatsBytesOut:
  249. s.BytesOut = native.Uint64(attr.Value)
  250. case ipvsSvcStatsCPS:
  251. s.CPS = native.Uint32(attr.Value)
  252. case ipvsSvcStatsPPSIn:
  253. s.PPSIn = native.Uint32(attr.Value)
  254. case ipvsSvcStatsPPSOut:
  255. s.PPSOut = native.Uint32(attr.Value)
  256. case ipvsSvcStatsBPSIn:
  257. s.BPSIn = native.Uint32(attr.Value)
  258. case ipvsSvcStatsBPSOut:
  259. s.BPSOut = native.Uint32(attr.Value)
  260. }
  261. }
  262. return s, nil
  263. }
  264. // assembleService assembles a services back from a hain of netlink attributes
  265. func assembleService(attrs []syscall.NetlinkRouteAttr) (*Service, error) {
  266. var s Service
  267. var addressBytes []byte
  268. for _, attr := range attrs {
  269. attrType := int(attr.Attr.Type)
  270. switch attrType {
  271. case ipvsSvcAttrAddressFamily:
  272. s.AddressFamily = native.Uint16(attr.Value)
  273. case ipvsSvcAttrProtocol:
  274. s.Protocol = native.Uint16(attr.Value)
  275. case ipvsSvcAttrAddress:
  276. addressBytes = attr.Value
  277. case ipvsSvcAttrPort:
  278. s.Port = binary.BigEndian.Uint16(attr.Value)
  279. case ipvsSvcAttrFWMark:
  280. s.FWMark = native.Uint32(attr.Value)
  281. case ipvsSvcAttrSchedName:
  282. s.SchedName = nl.BytesToString(attr.Value)
  283. case ipvsSvcAttrFlags:
  284. s.Flags = native.Uint32(attr.Value)
  285. case ipvsSvcAttrTimeout:
  286. s.Timeout = native.Uint32(attr.Value)
  287. case ipvsSvcAttrNetmask:
  288. s.Netmask = native.Uint32(attr.Value)
  289. case ipvsSvcAttrStats:
  290. stats, err := assembleStats(attr.Value)
  291. if err != nil {
  292. return nil, err
  293. }
  294. s.Stats = stats
  295. }
  296. }
  297. // parse Address after parse AddressFamily incase of parseIP error
  298. if addressBytes != nil {
  299. ip, err := parseIP(addressBytes, s.AddressFamily)
  300. if err != nil {
  301. return nil, err
  302. }
  303. s.Address = ip
  304. }
  305. return &s, nil
  306. }
  307. // parseService given a ipvs netlink response this function will respond with a valid service entry, an error otherwise
  308. func (i *Handle) parseService(msg []byte) (*Service, error) {
  309. var s *Service
  310. //Remove General header for this message and parse the NetLink message
  311. hdr := deserializeGenlMsg(msg)
  312. NetLinkAttrs, err := nl.ParseRouteAttr(msg[hdr.Len():])
  313. if err != nil {
  314. return nil, err
  315. }
  316. if len(NetLinkAttrs) == 0 {
  317. return nil, fmt.Errorf("error no valid netlink message found while parsing service record")
  318. }
  319. //Now Parse and get IPVS related attributes messages packed in this message.
  320. ipvsAttrs, err := nl.ParseRouteAttr(NetLinkAttrs[0].Value)
  321. if err != nil {
  322. return nil, err
  323. }
  324. //Assemble all the IPVS related attribute messages and create a service record
  325. s, err = assembleService(ipvsAttrs)
  326. if err != nil {
  327. return nil, err
  328. }
  329. return s, nil
  330. }
  331. // doGetServicesCmd a wrapper which could be used commonly for both GetServices() and GetService(*Service)
  332. func (i *Handle) doGetServicesCmd(svc *Service) ([]*Service, error) {
  333. var res []*Service
  334. msgs, err := i.doCmdwithResponse(svc, nil, ipvsCmdGetService)
  335. if err != nil {
  336. return nil, err
  337. }
  338. for _, msg := range msgs {
  339. srv, err := i.parseService(msg)
  340. if err != nil {
  341. return nil, err
  342. }
  343. res = append(res, srv)
  344. }
  345. return res, nil
  346. }
  347. // doCmdWithoutAttr a simple wrapper of netlink socket execute command
  348. func (i *Handle) doCmdWithoutAttr(cmd uint8) ([][]byte, error) {
  349. req := newIPVSRequest(cmd)
  350. req.Seq = atomic.AddUint32(&i.seq, 1)
  351. return execute(i.sock, req, 0)
  352. }
  353. func assembleDestination(attrs []syscall.NetlinkRouteAttr) (*Destination, error) {
  354. var d Destination
  355. var addressBytes []byte
  356. for _, attr := range attrs {
  357. attrType := int(attr.Attr.Type)
  358. switch attrType {
  359. case ipvsDestAttrAddressFamily:
  360. d.AddressFamily = native.Uint16(attr.Value)
  361. case ipvsDestAttrAddress:
  362. addressBytes = attr.Value
  363. case ipvsDestAttrPort:
  364. d.Port = binary.BigEndian.Uint16(attr.Value)
  365. case ipvsDestAttrForwardingMethod:
  366. d.ConnectionFlags = native.Uint32(attr.Value)
  367. case ipvsDestAttrWeight:
  368. d.Weight = int(native.Uint16(attr.Value))
  369. case ipvsDestAttrUpperThreshold:
  370. d.UpperThreshold = native.Uint32(attr.Value)
  371. case ipvsDestAttrLowerThreshold:
  372. d.LowerThreshold = native.Uint32(attr.Value)
  373. case ipvsDestAttrActiveConnections:
  374. d.ActiveConnections = int(native.Uint16(attr.Value))
  375. case ipvsDestAttrInactiveConnections:
  376. d.InactiveConnections = int(native.Uint16(attr.Value))
  377. case ipvsSvcAttrStats:
  378. stats, err := assembleStats(attr.Value)
  379. if err != nil {
  380. return nil, err
  381. }
  382. d.Stats = DstStats(stats)
  383. }
  384. }
  385. // parse Address after parse AddressFamily incase of parseIP error
  386. if addressBytes != nil {
  387. ip, err := parseIP(addressBytes, d.AddressFamily)
  388. if err != nil {
  389. return nil, err
  390. }
  391. d.Address = ip
  392. }
  393. return &d, nil
  394. }
  395. // parseDestination given a ipvs netlink response this function will respond with a valid destination entry, an error otherwise
  396. func (i *Handle) parseDestination(msg []byte) (*Destination, error) {
  397. var dst *Destination
  398. //Remove General header for this message
  399. hdr := deserializeGenlMsg(msg)
  400. NetLinkAttrs, err := nl.ParseRouteAttr(msg[hdr.Len():])
  401. if err != nil {
  402. return nil, err
  403. }
  404. if len(NetLinkAttrs) == 0 {
  405. return nil, fmt.Errorf("error no valid netlink message found while parsing destination record")
  406. }
  407. //Now Parse and get IPVS related attributes messages packed in this message.
  408. ipvsAttrs, err := nl.ParseRouteAttr(NetLinkAttrs[0].Value)
  409. if err != nil {
  410. return nil, err
  411. }
  412. //Assemble netlink attributes and create a Destination record
  413. dst, err = assembleDestination(ipvsAttrs)
  414. if err != nil {
  415. return nil, err
  416. }
  417. return dst, nil
  418. }
  419. // doGetDestinationsCmd a wrapper function to be used by GetDestinations and GetDestination(d) apis
  420. func (i *Handle) doGetDestinationsCmd(s *Service, d *Destination) ([]*Destination, error) {
  421. var res []*Destination
  422. msgs, err := i.doCmdwithResponse(s, d, ipvsCmdGetDest)
  423. if err != nil {
  424. return nil, err
  425. }
  426. for _, msg := range msgs {
  427. dest, err := i.parseDestination(msg)
  428. if err != nil {
  429. return res, err
  430. }
  431. res = append(res, dest)
  432. }
  433. return res, nil
  434. }
  435. // parseConfig given a ipvs netlink response this function will respond with a valid config entry, an error otherwise
  436. func (i *Handle) parseConfig(msg []byte) (*Config, error) {
  437. var c Config
  438. //Remove General header for this message
  439. hdr := deserializeGenlMsg(msg)
  440. attrs, err := nl.ParseRouteAttr(msg[hdr.Len():])
  441. if err != nil {
  442. return nil, err
  443. }
  444. for _, attr := range attrs {
  445. attrType := int(attr.Attr.Type)
  446. switch attrType {
  447. case ipvsCmdAttrTimeoutTCP:
  448. c.TimeoutTCP = time.Duration(native.Uint32(attr.Value)) * time.Second
  449. case ipvsCmdAttrTimeoutTCPFin:
  450. c.TimeoutTCPFin = time.Duration(native.Uint32(attr.Value)) * time.Second
  451. case ipvsCmdAttrTimeoutUDP:
  452. c.TimeoutUDP = time.Duration(native.Uint32(attr.Value)) * time.Second
  453. }
  454. }
  455. return &c, nil
  456. }
  457. // doGetConfigCmd a wrapper function to be used by GetConfig
  458. func (i *Handle) doGetConfigCmd() (*Config, error) {
  459. msg, err := i.doCmdWithoutAttr(ipvsCmdGetConfig)
  460. if err != nil {
  461. return nil, err
  462. }
  463. res, err := i.parseConfig(msg[0])
  464. if err != nil {
  465. return res, err
  466. }
  467. return res, nil
  468. }
  469. // doSetConfigCmd a wrapper function to be used by SetConfig
  470. func (i *Handle) doSetConfigCmd(c *Config) error {
  471. req := newIPVSRequest(ipvsCmdSetConfig)
  472. req.Seq = atomic.AddUint32(&i.seq, 1)
  473. req.AddData(nl.NewRtAttr(ipvsCmdAttrTimeoutTCP, nl.Uint32Attr(uint32(c.TimeoutTCP.Seconds()))))
  474. req.AddData(nl.NewRtAttr(ipvsCmdAttrTimeoutTCPFin, nl.Uint32Attr(uint32(c.TimeoutTCPFin.Seconds()))))
  475. req.AddData(nl.NewRtAttr(ipvsCmdAttrTimeoutUDP, nl.Uint32Attr(uint32(c.TimeoutUDP.Seconds()))))
  476. _, err := execute(i.sock, req, 0)
  477. return err
  478. }
  479. // IPVS related netlink message format explained
  480. /* EACH NETLINK MSG is of the below format, this is what we will receive from execute() api.
  481. If we have multiple netlink objects to process like GetServices() etc., execute() will
  482. supply an array of this below object
  483. NETLINK MSG
  484. |-----------------------------------|
  485. 0 1 2 3
  486. |--------|--------|--------|--------| -
  487. | CMD ID | VER | RESERVED | |==> General Message Header represented by genlMsgHdr
  488. |-----------------------------------| -
  489. | ATTR LEN | ATTR TYPE | |
  490. |-----------------------------------| |
  491. | | |
  492. | VALUE | |
  493. | []byte Array of IPVS MSG | |==> Attribute Message represented by syscall.NetlinkRouteAttr
  494. | PADDED BY 4 BYTES | |
  495. | | |
  496. |-----------------------------------| -
  497. Once We strip genlMsgHdr from above NETLINK MSG, we should parse the VALUE.
  498. VALUE will have an array of netlink attributes (syscall.NetlinkRouteAttr) such that each attribute will
  499. represent a "Service" or "Destination" object's field. If we assemble these attributes we can construct
  500. Service or Destination.
  501. IPVS MSG
  502. |-----------------------------------|
  503. 0 1 2 3
  504. |--------|--------|--------|--------|
  505. | ATTR LEN | ATTR TYPE |
  506. |-----------------------------------|
  507. | |
  508. | |
  509. | []byte IPVS ATTRIBUTE BY 4 BYTES |
  510. | |
  511. | |
  512. |-----------------------------------|
  513. NEXT ATTRIBUTE
  514. |-----------------------------------|
  515. | ATTR LEN | ATTR TYPE |
  516. |-----------------------------------|
  517. | |
  518. | |
  519. | []byte IPVS ATTRIBUTE BY 4 BYTES |
  520. | |
  521. | |
  522. |-----------------------------------|
  523. NEXT ATTRIBUTE
  524. |-----------------------------------|
  525. | ATTR LEN | ATTR TYPE |
  526. |-----------------------------------|
  527. | |
  528. | |
  529. | []byte IPVS ATTRIBUTE BY 4 BYTES |
  530. | |
  531. | |
  532. |-----------------------------------|
  533. */