{"id":1039,"date":"2022-02-24T14:34:36","date_gmt":"2022-02-24T14:34:36","guid":{"rendered":"https:\/\/www.ixeous.net\/cms\/?p=1039"},"modified":"2022-02-24T14:34:36","modified_gmt":"2022-02-24T14:34:36","slug":"geoip-filtering-with-nftables","status":"publish","type":"post","link":"https:\/\/www.ixeous.net\/cms\/index.php\/2022\/02\/24\/geoip-filtering-with-nftables\/","title":{"rendered":"GEOIP Filtering with nftables"},"content":{"rendered":"\r\n<p>Geoip filtering can be somewhat controversial. Rather than delve into any supposed benefits or effectiveness of the practice, this post is going to describe how to accomplish geoip filtering via nftables. The concept is simple. Create a set for the blocked IP ranges and simply drop traffic to or from the IPs in the set.<\/p>\r\n\r\n\r\n\r\n<p><strong>Getting the IP Database<\/strong><\/p>\r\n<p>Any geoip filtering is only as good as the data that matches IP addresses to their location.\u00a0 There are a few free options available.\u00a0 Most of these options use a freemium model where the free version has less granularity and fewer features than the paid version of their services.<\/p>\r\n<p>The service I use is IP2Location Lite which provides Class C level of accuracy.\u00a0 The main site is <a href=\"http:\/\/www.ip2location.com\">http:\/\/www.ip2location.com<\/a>.\u00a0 Information for the lite version can be found at <a href=\"http:\/\/lite.ip2location.com\">http:\/\/lite.ip2location.com<\/a>.\u00a0 The data is in a CSV file.\u00a0 The entries do not contain the actual IP address.\u00a0 Rather it is an encoded integer that reflects the IP.\u00a0 For example:<\/p>\r\n<pre>\"16777216\",\"16777471\",\"US\",\"United States of America\"<\/pre>\r\n<p>There are some significant advantages to storing the data this way, particularly for efficient searching, but for the purposes of creating nftables sets, the IP addresses will need to be decoded.\u00a0 This process is best done via a script shown later.<\/p>\r\n<p><strong>nftables<br \/><\/strong><\/p>\r\n<p>It&#8217;s helpful to understand some of the inner workings of nftables so that rules can be created efficiently.\u00a0 In nftables, a combination of hook and priority are used to determine packet flow.\u00a0 The image below shows the flow of packets based on the hook used.<\/p>\r\n<figure id=\"attachment_1041\" aria-describedby=\"caption-attachment-1041\" style=\"width: 700px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-1041 size-full\" src=\"https:\/\/www.ixeous.net\/cms\/wp-content\/uploads\/2022\/02\/nf-hooks-simple1.png\" alt=\"\" width=\"700\" height=\"219\" \/><figcaption id=\"caption-attachment-1041\" class=\"wp-caption-text\">Source: https:\/\/thermalcircle.de\/doku.php?id=blog:linux:nftables_packet_flow_netfilter_hooks_detail<\/figcaption><\/figure>\r\n<p>The second significant element is the priority assigned to the chain.\u00a0 From the nftables wiki:<\/p>\r\n<p style=\"padding-left: 40px;\">&#8220;Each nftables base\u00a0chain and flowtable is assigned a priority that defines its ordering among other base chains and flowtables and Netfilter internal operations at the same hook. For example, a chain on the <i>prerouting<\/i> hook with priority <i>-300<\/i> will be placed before connection tracking operations.&#8221;<\/p>\r\n<p>Because we already know that any traffic from or to the set of IP addresses will be dropped, those rules should be in place as early as possible.\u00a0 I use the &#8220;prerouting&#8221; hook and a priority of &#8220;raw&#8221; (-300) for the chain.\u00a0 This ensures that the packets are dropped before conntrack is ever involved.\u00a0 Although not technically necessary, I create a separate table for geo filtering to help keep the rules and sets organized and compartmentalized.\u00a0 The below commands will create the table, chain, set, and rules to block traffic to and from Antarctica.<\/p>\r\n<pre># nft add table geofilter<br \/># nft add chain ip geofilter PREROUTING { type filter hook prerouting priority raw \\; policy accept \\; }<br \/># nft add set ip geofilter GEOIP-AQ { type ipv4_addr \\; flags interval \\; auto-merge \\; }<br \/># nft add element ip geofilter GEOIP-AQ { 103.178.35.0 - 103.178.35.255 }<br \/># nft add rule ip geofilter PREROUTING ip saddr @GEOIP-AQ log prefix \"DROP_GEOIP-AQ_\" counter drop comment \"DROP_GEOIP-AQ\"<br \/># nft add rule ip geofilter PREROUTING ip daddr @GEOIP-AQ log prefix \"DROP_GEOIP-AQ_\" counter drop comment \"DROP_GEOIP-AQ\"<\/pre>\r\n<p>The result of the commands looks like<\/p>\r\n<pre># nft list table geofilter\r\n<br \/>table ip geofilter {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0set GEOIP-AQ {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0type ipv4_addr\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0flags interval\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0auto-merge\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0elements = { 103.178.35.0\/24 }\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n<br \/> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0chain PREROUTING {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0type filter hook prerouting priority raw; policy accept;\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ip saddr @GEOIP-AQ log prefix \"DROP_GEOIP-AQ\" counter packets 0 bytes 0 drop comment \"DROP_GEOIP-AQ\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ip daddr @GEOIP-AQ log prefix \"DROP_GEOIP-AQ\" counter packets 0 bytes 0 drop comment \"DROP_GEOIP-AQ\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n}\r\n<\/pre>\r\n<p>All other chains and rules will still be traversed.\u00a0 Someone from not Antarctica can still attempt to brute force SSH access, but if an input rule blocks SSH, they will be denied at that point.<\/p>\r\n<p>If instead of blocking Antarctica, I wanted to allow only Antarctica, I would change the policy for PREROUTING to drop and change the rules from drop to accept.\u00a0<\/p>\r\n<p><strong>Automating the process<br \/><\/strong><\/p>\r\n<p>Creating a small set for filtering can be accomplished manually, but if I wanted to filter the former Soviet states, it would be a long and tedious task to find and translate all of the entries from the IP database into IP addresses and then to add them to a set or multiple sets.\u00a0 If a new country needed to be added, the process would have to be repeated for those IPs as well.<\/p>\r\n<p>The script below was created to automatically build the table, sets, and rules for geoip blocking.\u00a0 To change the list of countries, simply modify as appropriate the line<\/p>\r\n<pre style=\"padding-left: 40px;\">blockCountries=( \"RU\" \"UA\" \"BY\" \"LT\" \"LV\" \"EE\" \"MD\" \"UZ\" \"KZ\" \"KG\" \"TJ\" \"TM\" \"GE\" \"AZ\" \"AM\" )<\/pre>\r\n<p>Create a cron job to run the script regularly and it will download a new copy of the database and add new entries.\u00a0 Don&#8217;t forget to save the rules so that they survive reboot.<\/p>\r\n<p>&nbsp;<\/p>\r\n<pre>#!\/bin\/bash\r\n<br \/>#Lets shift to some mathematics, Ip2Location use precise algorithm to calculate ip number from actual IP address.\r\n#Which is calculated by this formula : 16777216*w + 65536*x + 256*y + z\r\n#where w,x,y,z are the 1st part,2nd ,3rd and 4th part of IPv4 \u00a0address respectively.\r\n#\r\n#e.g if your ip address is 182.68.132.49 then w=182, x=68, y=132, z=49\r\n#ipNumber is =( 16777216 * 182 ) + ( 65536 * 68 ) + ( 256 * 132 ) + ( 49 ) = 3057943601\r\n<br \/>###############################################################################\r\n# GLOBAL VARIABLES\r\n###############################################################################\r\ndbFile=\"IP2LOCATION-LITE-DB1.CSV.ZIP\"\r\ndbDownloadURL=\"https:\/\/download.ip2location.com\/lite\"\r\ndataFile=\"IP2LOCATION-LITE-DB1.CSV\"\r\nnftCommand=\"\/usr\/sbin\/nft\"\r\nnftTable=\"geofilter\"\r\nnftFamily=\"ip\"\r\nnftChain=\"PREROUTING\"\r\nblockCountries=( \"RU\" \"UA\" \"BY\" \"LT\" \"LV\" \"EE\" \"MD\" \"UZ\" \"KZ\" \"KG\" \"TJ\" \"TM\" \"GE\" \"AZ\" \"AM\" )\r\n<br \/>###############################################################################\r\n# END GLOBAL VARIABLES\r\n###############################################################################\r\n<br \/>###############################################################################\r\n# FUNCTIONS\r\n###############################################################################\r\n<br \/>###############################################################################\r\n#\r\n# decodeIP\r\n#\r\n# Decodes the IP as given in the database file to return A.B.C.D\r\n# Requires one parameter\r\n#\r\n# formula to decode ip address:\r\n#\r\n# fourthOctet = value % 256\r\n# thirdOctet = ((value - fourthOctet) \/ 256) % 256\r\n# secondOctet = (((value - fourthOctet) - (256 * thirdOctet)) \/ 65536) % 256\r\n# firstOctet = ((((value - fourthOctet) - (256 * thirdOctet) - (65536 * secondOctet)) \/ 16777216) % 256\r\n#\r\n###############################################################################\r\nfunction decodeIP ()\r\n{\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if [ $# -ne 1 ]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"ERROR: decodeIP requires a parameter. Check calls to the function\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0exit 1\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fi\r\n\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0local encodedIPValue=\"$1\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fourthOctet=$(( encodedIPValue % 256 ))\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0thirdOctet=$(( $(( (encodedIPValue - ${fourthOctet}) \/ 256 )) % 256 ))\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0secondOctet=$(( $(( (encodedIPValue - ${fourthOctet} - $(( ${thirdOctet} * 256 ))) \/ 65536 )) % 256 ))\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0firstOctet=$(( $(( (encodedIPValue - ${fourthOctet} - $(( ${thirdOctet} * 256 )) - $(( ${secondOctet} * 65536 ))) \/ 16777216 )) % 256 ))\r\n<br \/> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"${firstOctet}.${secondOctet}.${thirdOctet}.${fourthOctet}\"\r\n}\r\n<br \/>###############################################################################\r\n# END FUNCTIONS\r\n###############################################################################\r\n<br \/># Download and extract an updated data file\r\nif [ -f ${dbFile} ]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0rm -f ${dbFile}\r\nfi\r\n<br \/># Download latest db file\r\n\/usr\/bin\/curl -o ${dbFile} ${dbDownloadURL}\/${dbFile}\r\n\/usr\/bin\/unzip -f -o ${dbFile}\r\n\r\n# Make sure that the table and chain exist\r\nif [[ $(${nftCommand} list tables | grep ${nftTable}) ]]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0: # do nothing\r\nelse\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${nftCommand} add table ${nftFamily} ${nftTable}\r\nfi\r\nif [[ $(${nftCommand} list chain ${nftFamily} ${nftTable} ${nftChain}) ]]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0: # do nothing\r\nelse\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${nftCommand} add chain ${nftFamily} ${nftTable} ${nftChain} { type filter hook prerouting priority raw \\; policy accept \\; }\r\nfi\r\n<br \/># Build all sets and rules\r\nfor ((i=0;i&lt;${#blockCountries[@]};i++)); do\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# create the set if it does not exist\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# set will be named GEOIP-${countryCode}\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"Creating set GEOIP-${blockCountries[${i}]}\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if [[ $(${nftCommand} list sets | grep GEOIP-${blockCountries[${i}]}) ]]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0: # do nothing\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${nftCommand} add set ${nftFamily} ${nftTable} GEOIP-${blockCountries[${i}]} { type ipv4_addr \\; flags interval \\; auto-merge \\; }\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fi\r\n<br \/> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# Populate the set with IP ranges from the database file\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# Use grep to filter db to just the country currently in scope\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"Populating set GEOIP-${blockCountries[${i}]}\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0grep ${blockCountries[${i}]} ${dataFile} | awk -F\\\" '{print $2\" \"$4\" \"$6}' | while IFS=\" \" read -r fromIPCode toIPCode countryCode\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0do\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fromIP=$(decodeIP ${fromIPCode})\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0toIP=$(decodeIP ${toIPCode})\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${nftCommand} add element ${nftFamily} ${nftTable} GEOIP-${countryCode} { ${fromIP}-${toIP} }\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0done\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"Population of GEOIP-${blockCountries[${i}]} complete\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"Updating nftables rules for GEOIP-${blockCountries[${i}]}\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# Each blocked country will have 2 rules to deny.\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# One for saddr and one for daddr. \u00a0GeoIP will be the first drops\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# daddr\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if [[ $(${nftCommand} list chain ${nftFamily} ${nftTable} ${nftChain} | grep \"ip daddr @GEOIP-${blockCountries[${i}]}\") ]]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0: # found rule - do nothing\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${nftCommand} insert rule ${nftFamily} ${nftTable} ${nftChain} ip daddr @GEOIP-${blockCountries[${i}]} log prefix \"DROP_GEOIP-${blockCountries[${i}]}_\" counter drop comment \"DROP_GEOIP-${blockCountries[${i}]}\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fi\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0# saddr\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if [[ $(${nftCommand} list chain ${nftFamily} ${nftTable} ${nftChain} | grep \"ip saddr @GEOIP-${blockCountries[${i}]}\") ]]; then\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0: # found rule - do nothing\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${nftCommand} insert rule ${nftFamily} ${nftTable} ${nftChain} ip saddr @GEOIP-${blockCountries[${i}]} log prefix \"DROP_GEOIP-${blockCountries[${i}]}_\" counter drop comment \"DROP_GEOIP-${blockCountries[${i}]}\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fi\r\n<br \/> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0echo \"Updating rules for GEOIP-${blockCountries[${i}]} complete\"\r\ndone\r\n<\/pre>\r\n<p>Note that this script does not remove entries from the list.\u00a0 It will only add to it.\u00a0 Any removal will need to be done manually.\u00a0 Hopefully this has been helpful.<\/p>\r\n","protected":false},"excerpt":{"rendered":"<p>Geoip filtering can be somewhat controversial. Rather than delve into any supposed benefits or effectiveness of the practice, this post is going to describe how to accomplish geoip filtering via nftables. The concept is simple. Create a set for the blocked IP ranges and simply drop traffic to or from the IPs in the set. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[],"tags":[],"class_list":["post-1039","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/posts\/1039","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/comments?post=1039"}],"version-history":[{"count":21,"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/posts\/1039\/revisions"}],"predecessor-version":[{"id":1062,"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/posts\/1039\/revisions\/1062"}],"wp:attachment":[{"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/media?parent=1039"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/categories?post=1039"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ixeous.net\/cms\/index.php\/wp-json\/wp\/v2\/tags?post=1039"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}