github.com/sagernet/netlink@v0.0.0-20240612041022-b9a21c07ac6a/ipset_linux.go (about)

     1  package netlink
     2  
     3  import (
     4  	"encoding/binary"
     5  	"log"
     6  	"net"
     7  	"syscall"
     8  
     9  	"github.com/sagernet/netlink/nl"
    10  	"golang.org/x/sys/unix"
    11  )
    12  
    13  // IPSetEntry is used for adding, updating, retreiving and deleting entries
    14  type IPSetEntry struct {
    15  	Comment  string
    16  	MAC      net.HardwareAddr
    17  	IP       net.IP
    18  	CIDR     uint8
    19  	Timeout  *uint32
    20  	Packets  *uint64
    21  	Bytes    *uint64
    22  	Protocol *uint8
    23  	Port     *uint16
    24  	IP2      net.IP
    25  	CIDR2    uint8
    26  	IFace    string
    27  	Mark     *uint32
    28  
    29  	Replace bool // replace existing entry
    30  }
    31  
    32  // IPSetResult is the result of a dump request for a set
    33  type IPSetResult struct {
    34  	Nfgenmsg           *nl.Nfgenmsg
    35  	Protocol           uint8
    36  	ProtocolMinVersion uint8
    37  	Revision           uint8
    38  	Family             uint8
    39  	Flags              uint8
    40  	SetName            string
    41  	TypeName           string
    42  	Comment            string
    43  	MarkMask           uint32
    44  
    45  	IPFrom   net.IP
    46  	IPTo     net.IP
    47  	PortFrom uint16
    48  	PortTo   uint16
    49  
    50  	HashSize     uint32
    51  	NumEntries   uint32
    52  	MaxElements  uint32
    53  	References   uint32
    54  	SizeInMemory uint32
    55  	CadtFlags    uint32
    56  	Timeout      *uint32
    57  	LineNo       uint32
    58  
    59  	Entries []IPSetEntry
    60  }
    61  
    62  // IpsetCreateOptions is the options struct for creating a new ipset
    63  type IpsetCreateOptions struct {
    64  	Replace  bool // replace existing ipset
    65  	Timeout  *uint32
    66  	Counters bool
    67  	Comments bool
    68  	Skbinfo  bool
    69  
    70  	Revision uint8
    71  	IPFrom   net.IP
    72  	IPTo     net.IP
    73  	PortFrom uint16
    74  	PortTo   uint16
    75  }
    76  
    77  // IpsetProtocol returns the ipset protocol version from the kernel
    78  func IpsetProtocol() (uint8, uint8, error) {
    79  	return pkgHandle.IpsetProtocol()
    80  }
    81  
    82  // IpsetCreate creates a new ipset
    83  func IpsetCreate(setname, typename string, options IpsetCreateOptions) error {
    84  	return pkgHandle.IpsetCreate(setname, typename, options)
    85  }
    86  
    87  // IpsetDestroy destroys an existing ipset
    88  func IpsetDestroy(setname string) error {
    89  	return pkgHandle.IpsetDestroy(setname)
    90  }
    91  
    92  // IpsetFlush flushes an existing ipset
    93  func IpsetFlush(setname string) error {
    94  	return pkgHandle.IpsetFlush(setname)
    95  }
    96  
    97  // IpsetSwap swaps two ipsets.
    98  func IpsetSwap(setname, othersetname string) error {
    99  	return pkgHandle.IpsetSwap(setname, othersetname)
   100  }
   101  
   102  // IpsetList dumps an specific ipset.
   103  func IpsetList(setname string) (*IPSetResult, error) {
   104  	return pkgHandle.IpsetList(setname)
   105  }
   106  
   107  // IpsetListAll dumps all ipsets.
   108  func IpsetListAll() ([]IPSetResult, error) {
   109  	return pkgHandle.IpsetListAll()
   110  }
   111  
   112  // IpsetAdd adds an entry to an existing ipset.
   113  func IpsetAdd(setname string, entry *IPSetEntry) error {
   114  	return pkgHandle.IpsetAdd(setname, entry)
   115  }
   116  
   117  // IpsetDel deletes an entry from an existing ipset.
   118  func IpsetDel(setname string, entry *IPSetEntry) error {
   119  	return pkgHandle.IpsetDel(setname, entry)
   120  }
   121  
   122  func (h *Handle) IpsetProtocol() (protocol uint8, minVersion uint8, err error) {
   123  	req := h.newIpsetRequest(nl.IPSET_CMD_PROTOCOL)
   124  	msgs, err := req.Execute(unix.NETLINK_NETFILTER, 0)
   125  
   126  	if err != nil {
   127  		return 0, 0, err
   128  	}
   129  	response := ipsetUnserialize(msgs)
   130  	return response.Protocol, response.ProtocolMinVersion, nil
   131  }
   132  
   133  func (h *Handle) IpsetCreate(setname, typename string, options IpsetCreateOptions) error {
   134  	req := h.newIpsetRequest(nl.IPSET_CMD_CREATE)
   135  
   136  	if !options.Replace {
   137  		req.Flags |= unix.NLM_F_EXCL
   138  	}
   139  
   140  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname)))
   141  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_TYPENAME, nl.ZeroTerminated(typename)))
   142  
   143  	revision := options.Revision
   144  	if revision == 0 {
   145  		revision = getIpsetDefaultWithTypeName(typename)
   146  	}
   147  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_REVISION, nl.Uint8Attr(revision)))
   148  
   149  	data := nl.NewRtAttr(nl.IPSET_ATTR_DATA|int(nl.NLA_F_NESTED), nil)
   150  
   151  	var family uint8
   152  	switch typename {
   153  	case "hash:mac":
   154  	case "bitmap:port":
   155  		buf := make([]byte, 4)
   156  		binary.BigEndian.PutUint16(buf, options.PortFrom)
   157  		binary.BigEndian.PutUint16(buf[2:], options.PortTo)
   158  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_PORT_FROM|int(nl.NLA_F_NET_BYTEORDER), buf[:2]))
   159  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_PORT_TO|int(nl.NLA_F_NET_BYTEORDER), buf[2:]))
   160  	default:
   161  		family = unix.AF_INET
   162  	}
   163  
   164  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_FAMILY, nl.Uint8Attr(family)))
   165  
   166  	if timeout := options.Timeout; timeout != nil {
   167  		data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER, Value: *timeout})
   168  	}
   169  
   170  	var cadtFlags uint32
   171  
   172  	if options.Comments {
   173  		cadtFlags |= nl.IPSET_FLAG_WITH_COMMENT
   174  	}
   175  	if options.Counters {
   176  		cadtFlags |= nl.IPSET_FLAG_WITH_COUNTERS
   177  	}
   178  	if options.Skbinfo {
   179  		cadtFlags |= nl.IPSET_FLAG_WITH_SKBINFO
   180  	}
   181  
   182  	if cadtFlags != 0 {
   183  		data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_CADT_FLAGS | nl.NLA_F_NET_BYTEORDER, Value: cadtFlags})
   184  	}
   185  
   186  	req.AddData(data)
   187  	_, err := ipsetExecute(req)
   188  	return err
   189  }
   190  
   191  func (h *Handle) IpsetDestroy(setname string) error {
   192  	req := h.newIpsetRequest(nl.IPSET_CMD_DESTROY)
   193  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname)))
   194  	_, err := ipsetExecute(req)
   195  	return err
   196  }
   197  
   198  func (h *Handle) IpsetFlush(setname string) error {
   199  	req := h.newIpsetRequest(nl.IPSET_CMD_FLUSH)
   200  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname)))
   201  	_, err := ipsetExecute(req)
   202  	return err
   203  }
   204  
   205  func (h *Handle) IpsetSwap(setname, othersetname string) error {
   206  	req := h.newIpsetRequest(nl.IPSET_CMD_SWAP)
   207  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname)))
   208  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_TYPENAME, nl.ZeroTerminated(othersetname)))
   209  	_, err := ipsetExecute(req)
   210  	return err
   211  }
   212  
   213  func (h *Handle) IpsetList(name string) (*IPSetResult, error) {
   214  	req := h.newIpsetRequest(nl.IPSET_CMD_LIST)
   215  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(name)))
   216  
   217  	msgs, err := ipsetExecute(req)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	result := ipsetUnserialize(msgs)
   223  	return &result, nil
   224  }
   225  
   226  func (h *Handle) IpsetListAll() ([]IPSetResult, error) {
   227  	req := h.newIpsetRequest(nl.IPSET_CMD_LIST)
   228  
   229  	msgs, err := ipsetExecute(req)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	result := make([]IPSetResult, len(msgs))
   235  	for i, msg := range msgs {
   236  		result[i].unserialize(msg)
   237  	}
   238  
   239  	return result, nil
   240  }
   241  
   242  // IpsetAdd adds an entry to an existing ipset.
   243  func (h *Handle) IpsetAdd(setname string, entry *IPSetEntry) error {
   244  	return h.ipsetAddDel(nl.IPSET_CMD_ADD, setname, entry)
   245  }
   246  
   247  // IpsetDel deletes an entry from an existing ipset.
   248  func (h *Handle) IpsetDel(setname string, entry *IPSetEntry) error {
   249  	return h.ipsetAddDel(nl.IPSET_CMD_DEL, setname, entry)
   250  }
   251  
   252  func (h *Handle) ipsetAddDel(nlCmd int, setname string, entry *IPSetEntry) error {
   253  	req := h.newIpsetRequest(nlCmd)
   254  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_SETNAME, nl.ZeroTerminated(setname)))
   255  
   256  	if entry.Comment != "" {
   257  		req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_COMMENT, nl.ZeroTerminated(entry.Comment)))
   258  	}
   259  
   260  	data := nl.NewRtAttr(nl.IPSET_ATTR_DATA|int(nl.NLA_F_NESTED), nil)
   261  
   262  	if !entry.Replace {
   263  		req.Flags |= unix.NLM_F_EXCL
   264  	}
   265  
   266  	if entry.Timeout != nil {
   267  		data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER, Value: *entry.Timeout})
   268  	}
   269  
   270  	if entry.IP != nil {
   271  		nestedData := nl.NewRtAttr(nl.IPSET_ATTR_IP|int(nl.NLA_F_NET_BYTEORDER), entry.IP)
   272  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_IP|int(nl.NLA_F_NESTED), nestedData.Serialize()))
   273  	}
   274  
   275  	if entry.MAC != nil {
   276  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_ETHER, entry.MAC))
   277  	}
   278  
   279  	if entry.CIDR != 0 {
   280  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_CIDR, nl.Uint8Attr(entry.CIDR)))
   281  	}
   282  
   283  	if entry.IP2 != nil {
   284  		nestedData := nl.NewRtAttr(nl.IPSET_ATTR_IP|int(nl.NLA_F_NET_BYTEORDER), entry.IP2)
   285  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_IP2|int(nl.NLA_F_NESTED), nestedData.Serialize()))
   286  	}
   287  
   288  	if entry.CIDR2 != 0 {
   289  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_CIDR2, nl.Uint8Attr(entry.CIDR2)))
   290  	}
   291  
   292  	if entry.Port != nil {
   293  		if entry.Protocol == nil {
   294  			// use tcp protocol as default
   295  			val := uint8(unix.IPPROTO_TCP)
   296  			entry.Protocol = &val
   297  		}
   298  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_PROTO, nl.Uint8Attr(*entry.Protocol)))
   299  		buf := make([]byte, 2)
   300  		binary.BigEndian.PutUint16(buf, *entry.Port)
   301  		data.AddChild(nl.NewRtAttr(int(nl.IPSET_ATTR_PORT|nl.NLA_F_NET_BYTEORDER), buf))
   302  	}
   303  
   304  	if entry.IFace != "" {
   305  		data.AddChild(nl.NewRtAttr(nl.IPSET_ATTR_IFACE, nl.ZeroTerminated(entry.IFace)))
   306  	}
   307  
   308  	if entry.Mark != nil {
   309  		data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_MARK | nl.NLA_F_NET_BYTEORDER, Value: *entry.Mark})
   310  	}
   311  
   312  	data.AddChild(&nl.Uint32Attribute{Type: nl.IPSET_ATTR_LINENO | nl.NLA_F_NET_BYTEORDER, Value: 0})
   313  	req.AddData(data)
   314  
   315  	_, err := ipsetExecute(req)
   316  	return err
   317  }
   318  
   319  func (h *Handle) newIpsetRequest(cmd int) *nl.NetlinkRequest {
   320  	req := h.newNetlinkRequest(cmd|(unix.NFNL_SUBSYS_IPSET<<8), nl.GetIpsetFlags(cmd))
   321  
   322  	// Add the netfilter header
   323  	msg := &nl.Nfgenmsg{
   324  		NfgenFamily: uint8(unix.AF_NETLINK),
   325  		Version:     nl.NFNETLINK_V0,
   326  		ResId:       0,
   327  	}
   328  	req.AddData(msg)
   329  	req.AddData(nl.NewRtAttr(nl.IPSET_ATTR_PROTOCOL, nl.Uint8Attr(nl.IPSET_PROTOCOL)))
   330  
   331  	return req
   332  }
   333  
   334  func getIpsetDefaultWithTypeName(typename string) uint8 {
   335  	switch typename {
   336  	case "hash:ip,port",
   337  		"hash:ip,port,ip",
   338  		"hash:ip,port,net",
   339  		"hash:net,port":
   340  		return 1
   341  	}
   342  	return 0
   343  }
   344  
   345  func ipsetExecute(req *nl.NetlinkRequest) (msgs [][]byte, err error) {
   346  	msgs, err = req.Execute(unix.NETLINK_NETFILTER, 0)
   347  
   348  	if err != nil {
   349  		if errno := int(err.(syscall.Errno)); errno >= nl.IPSET_ERR_PRIVATE {
   350  			err = nl.IPSetError(uintptr(errno))
   351  		}
   352  	}
   353  	return
   354  }
   355  
   356  func ipsetUnserialize(msgs [][]byte) (result IPSetResult) {
   357  	for _, msg := range msgs {
   358  		result.unserialize(msg)
   359  	}
   360  	return result
   361  }
   362  
   363  func (result *IPSetResult) unserialize(msg []byte) {
   364  	result.Nfgenmsg = nl.DeserializeNfgenmsg(msg)
   365  
   366  	for attr := range nl.ParseAttributes(msg[4:]) {
   367  		switch attr.Type {
   368  		case nl.IPSET_ATTR_PROTOCOL:
   369  			result.Protocol = attr.Value[0]
   370  		case nl.IPSET_ATTR_SETNAME:
   371  			result.SetName = nl.BytesToString(attr.Value)
   372  		case nl.IPSET_ATTR_COMMENT:
   373  			result.Comment = nl.BytesToString(attr.Value)
   374  		case nl.IPSET_ATTR_TYPENAME:
   375  			result.TypeName = nl.BytesToString(attr.Value)
   376  		case nl.IPSET_ATTR_REVISION:
   377  			result.Revision = attr.Value[0]
   378  		case nl.IPSET_ATTR_FAMILY:
   379  			result.Family = attr.Value[0]
   380  		case nl.IPSET_ATTR_FLAGS:
   381  			result.Flags = attr.Value[0]
   382  		case nl.IPSET_ATTR_DATA | nl.NLA_F_NESTED:
   383  			result.parseAttrData(attr.Value)
   384  		case nl.IPSET_ATTR_ADT | nl.NLA_F_NESTED:
   385  			result.parseAttrADT(attr.Value)
   386  		case nl.IPSET_ATTR_PROTOCOL_MIN:
   387  			result.ProtocolMinVersion = attr.Value[0]
   388  		case nl.IPSET_ATTR_MARKMASK:
   389  			result.MarkMask = attr.Uint32()
   390  		default:
   391  			log.Printf("unknown ipset attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK)
   392  		}
   393  	}
   394  }
   395  
   396  func (result *IPSetResult) parseAttrData(data []byte) {
   397  	for attr := range nl.ParseAttributes(data) {
   398  		switch attr.Type {
   399  		case nl.IPSET_ATTR_HASHSIZE | nl.NLA_F_NET_BYTEORDER:
   400  			result.HashSize = attr.Uint32()
   401  		case nl.IPSET_ATTR_MAXELEM | nl.NLA_F_NET_BYTEORDER:
   402  			result.MaxElements = attr.Uint32()
   403  		case nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER:
   404  			val := attr.Uint32()
   405  			result.Timeout = &val
   406  		case nl.IPSET_ATTR_ELEMENTS | nl.NLA_F_NET_BYTEORDER:
   407  			result.NumEntries = attr.Uint32()
   408  		case nl.IPSET_ATTR_REFERENCES | nl.NLA_F_NET_BYTEORDER:
   409  			result.References = attr.Uint32()
   410  		case nl.IPSET_ATTR_MEMSIZE | nl.NLA_F_NET_BYTEORDER:
   411  			result.SizeInMemory = attr.Uint32()
   412  		case nl.IPSET_ATTR_CADT_FLAGS | nl.NLA_F_NET_BYTEORDER:
   413  			result.CadtFlags = attr.Uint32()
   414  		case nl.IPSET_ATTR_IP | nl.NLA_F_NESTED:
   415  			for nested := range nl.ParseAttributes(attr.Value) {
   416  				switch nested.Type {
   417  				case nl.IPSET_ATTR_IP | nl.NLA_F_NET_BYTEORDER:
   418  					result.Entries = append(result.Entries, IPSetEntry{IP: nested.Value})
   419  				case nl.IPSET_ATTR_IP:
   420  					result.IPFrom = nested.Value
   421  				default:
   422  					log.Printf("unknown nested ipset data attribute from kernel: %+v %v", nested, nested.Type&nl.NLA_TYPE_MASK)
   423  				}
   424  			}
   425  		case nl.IPSET_ATTR_IP_TO | nl.NLA_F_NESTED:
   426  			for nested := range nl.ParseAttributes(attr.Value) {
   427  				switch nested.Type {
   428  				case nl.IPSET_ATTR_IP:
   429  					result.IPTo = nested.Value
   430  				default:
   431  					log.Printf("unknown nested ipset data attribute from kernel: %+v %v", nested, nested.Type&nl.NLA_TYPE_MASK)
   432  				}
   433  			}
   434  		case nl.IPSET_ATTR_PORT_FROM | nl.NLA_F_NET_BYTEORDER:
   435  			result.PortFrom = networkOrder.Uint16(attr.Value)
   436  		case nl.IPSET_ATTR_PORT_TO | nl.NLA_F_NET_BYTEORDER:
   437  			result.PortTo = networkOrder.Uint16(attr.Value)
   438  		case nl.IPSET_ATTR_CADT_LINENO | nl.NLA_F_NET_BYTEORDER:
   439  			result.LineNo = attr.Uint32()
   440  		case nl.IPSET_ATTR_COMMENT:
   441  			result.Comment = nl.BytesToString(attr.Value)
   442  		case nl.IPSET_ATTR_MARKMASK:
   443  			result.MarkMask = attr.Uint32()
   444  		default:
   445  			log.Printf("unknown ipset data attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK)
   446  		}
   447  	}
   448  }
   449  
   450  func (result *IPSetResult) parseAttrADT(data []byte) {
   451  	for attr := range nl.ParseAttributes(data) {
   452  		switch attr.Type {
   453  		case nl.IPSET_ATTR_DATA | nl.NLA_F_NESTED:
   454  			result.Entries = append(result.Entries, parseIPSetEntry(attr.Value))
   455  		default:
   456  			log.Printf("unknown ADT attribute from kernel: %+v %v", attr, attr.Type&nl.NLA_TYPE_MASK)
   457  		}
   458  	}
   459  }
   460  
   461  func parseIPSetEntry(data []byte) (entry IPSetEntry) {
   462  	for attr := range nl.ParseAttributes(data) {
   463  		switch attr.Type {
   464  		case nl.IPSET_ATTR_TIMEOUT | nl.NLA_F_NET_BYTEORDER:
   465  			val := attr.Uint32()
   466  			entry.Timeout = &val
   467  		case nl.IPSET_ATTR_BYTES | nl.NLA_F_NET_BYTEORDER:
   468  			val := attr.Uint64()
   469  			entry.Bytes = &val
   470  		case nl.IPSET_ATTR_PACKETS | nl.NLA_F_NET_BYTEORDER:
   471  			val := attr.Uint64()
   472  			entry.Packets = &val
   473  		case nl.IPSET_ATTR_ETHER:
   474  			entry.MAC = net.HardwareAddr(attr.Value)
   475  		case nl.IPSET_ATTR_IP:
   476  			entry.IP = net.IP(attr.Value)
   477  		case nl.IPSET_ATTR_COMMENT:
   478  			entry.Comment = nl.BytesToString(attr.Value)
   479  		case nl.IPSET_ATTR_IP | nl.NLA_F_NESTED:
   480  			for attr := range nl.ParseAttributes(attr.Value) {
   481  				switch attr.Type {
   482  				case nl.IPSET_ATTR_IP:
   483  					entry.IP = net.IP(attr.Value)
   484  				default:
   485  					log.Printf("unknown nested ADT attribute from kernel: %+v", attr)
   486  				}
   487  			}
   488  		case nl.IPSET_ATTR_IP2 | nl.NLA_F_NESTED:
   489  			for attr := range nl.ParseAttributes(attr.Value) {
   490  				switch attr.Type {
   491  				case nl.IPSET_ATTR_IP:
   492  					entry.IP2 = net.IP(attr.Value)
   493  				default:
   494  					log.Printf("unknown nested ADT attribute from kernel: %+v", attr)
   495  				}
   496  			}
   497  		case nl.IPSET_ATTR_CIDR:
   498  			entry.CIDR = attr.Value[0]
   499  		case nl.IPSET_ATTR_CIDR2:
   500  			entry.CIDR2 = attr.Value[0]
   501  		case nl.IPSET_ATTR_PORT | nl.NLA_F_NET_BYTEORDER:
   502  			val := networkOrder.Uint16(attr.Value)
   503  			entry.Port = &val
   504  		case nl.IPSET_ATTR_PROTO:
   505  			val := attr.Value[0]
   506  			entry.Protocol = &val
   507  		case nl.IPSET_ATTR_IFACE:
   508  			entry.IFace = nl.BytesToString(attr.Value)
   509  		case nl.IPSET_ATTR_MARK | nl.NLA_F_NET_BYTEORDER:
   510  			val := attr.Uint32()
   511  			entry.Mark = &val
   512  		default:
   513  			log.Printf("unknown ADT attribute from kernel: %+v", attr)
   514  		}
   515  	}
   516  	return
   517  }