{"id":20,"date":"2005-06-01T20:08:52","date_gmt":"2005-06-01T12:08:52","guid":{"rendered":"http:\/\/www.hmqq.net\/?p=20"},"modified":"2011-06-16T21:28:59","modified_gmt":"2011-06-16T13:28:59","slug":"python-ping","status":"publish","type":"post","link":"https:\/\/minqiao.me\/?p=20","title":{"rendered":"Python ping"},"content":{"rendered":"<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n#!\/usr\/bin\/env python\r\n# -*- coding: iso-8859-1 -*-\r\n&quot;&quot;&quot;ping.py\r\n \r\n ping.py uses the ICMP protocol's mandatory ECHO_REQUEST\r\n datagram to elicit an ICMP ECHO_RESPONSE from a\r\n host or gateway.\r\n\r\n Copyright (C) 2004 - Lars Strand (lars strand at gnist org)\r\n \r\n This program is free software; you can redistribute it and\/or\r\n modify it under the terms of the GNU General Public License\r\n as published by the Free Software Foundation; either version 2\r\n of the License, or (at your option) any later version.\r\n \r\n This program is distributed in the hope that it will be useful,\r\n but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n GNU General Public License for more details.\r\n \r\n You should have received a copy of the GNU General Public License\r\n along with this program; if not, write to the Free Software\r\n Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r\n\r\n Must be running as root, or write a suid-wrapper. Since newer *nix\r\n variants, the kernel ignores the set&#x5B;ug]id flags on #! scripts for\r\n security reasons\r\n\r\n RFC792, echo\/reply message:\r\n\r\n  0                   1                   2                   3\r\n  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\r\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\r\n |     Type      |     Code      |          Checksum             |\r\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\r\n |           Identifier          |        Sequence Number        |\r\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\r\n |     Data ...\r\n +-+-+-+-+-\r\n\r\n\r\nTODO:\r\n - do not create socket inside 'while' (but if not: ipv6 won't work)\r\n - add support for broadcast\/multicast\r\n - add support for own payload string\r\n\r\nCHANGELOG:\r\n DONE --&gt; bugfix from Filip Van Raemdonck mechanix debian org\r\n DONE --&gt; add more support for modules (raise instead of sys.exit)\r\n DONE --&gt; locale func names\r\n DONE --&gt; package def\r\n DONE --&gt; some code cleanup\r\n \r\n&quot;&quot;&quot;\r\n\r\nimport sys\r\nimport os\r\nimport struct\r\nimport array\r\nimport time\r\nimport select\r\nimport binascii\r\nimport math\r\nimport getopt\r\nimport string\r\nimport socket\r\n\r\n# total size of data (payload)\r\nICMP_DATA_STR = 56  \r\n\r\n# initial values of header variables\r\nICMP_TYPE = 8\r\nICMP_TYPE_IP6 = 128\r\nICMP_CODE = 0\r\nICMP_CHECKSUM = 0\r\nICMP_ID = 0\r\nICMP_SEQ_NR = 0\r\n\r\n# Package definitions.\r\n__program__   = 'ping'\r\n__version__   = '0.5a'\r\n__date__      = '2004\/15\/12'\r\n__author__    = 'Lars Strand &lt;lars at unik no&gt;'\r\n__licence__   = 'GPL'\r\n__copyright__ = 'Copyright (C) 2004 Lars Strand'\r\n\r\ndef _construct(id, size, ipv6):\r\n    &quot;&quot;&quot;Constructs a ICMP echo packet of variable size\r\n    &quot;&quot;&quot;\r\n\r\n    # size must be big enough to contain time sent\r\n    if size &lt; int(struct.calcsize(&quot;d&quot;)):\r\n        _error(&quot;packetsize to small, must be at least %d&quot; % int(struct.calcsize(&quot;d&quot;)))\r\n    \r\n    # construct header\r\n    if ipv6:\r\n        header = struct.pack('BbHHh', ICMP_TYPE_IP6, ICMP_CODE, ICMP_CHECKSUM, \\\r\n                             ICMP_ID, ICMP_SEQ_NR+id)\r\n    else:\r\n        header = struct.pack('bbHHh', ICMP_TYPE, ICMP_CODE, ICMP_CHECKSUM, \\\r\n                             ICMP_ID, ICMP_SEQ_NR+id)\r\n\r\n    # if size big enough, embed this payload\r\n    load = &quot;-- IF YOU ARE READING THIS YOU ARE A NERD! --&quot;\r\n    \r\n    # space for time\r\n    size -= struct.calcsize(&quot;d&quot;)\r\n\r\n    # construct payload based on size, may be omitted :)\r\n    rest = &quot;&quot;\r\n    if size &gt; len(load):\r\n        rest = load\r\n        size -= len(load)\r\n\r\n    # pad the rest of payload\r\n    rest += size * &quot;X&quot;\r\n\r\n    # pack\r\n    data = struct.pack(&quot;d&quot;, time.time()) + rest\r\n    packet = header + data          # ping packet without checksum\r\n    checksum = _in_cksum(packet)    # make checksum\r\n\r\n    # construct header with correct checksum\r\n    if ipv6:\r\n        header = struct.pack('BbHHh', ICMP_TYPE_IP6, ICMP_CODE, checksum, \\\r\n                             ICMP_ID, ICMP_SEQ_NR+id)\r\n    else:\r\n        header = struct.pack('bbHHh', ICMP_TYPE, ICMP_CODE, checksum, ICMP_ID, \\\r\n                             ICMP_SEQ_NR+id)\r\n\r\n    # ping packet *with* checksum\r\n    packet = header + data \r\n\r\n    # a perfectly formatted ICMP echo packet\r\n    return packet\r\n\r\ndef _in_cksum(packet):\r\n    &quot;&quot;&quot;THE RFC792 states: 'The 16 bit one's complement of\r\n    the one's complement sum of all 16 bit words in the header.'\r\n\r\n    Generates a checksum of a (ICMP) packet. Based on in_chksum found\r\n    in ping.c on FreeBSD.\r\n    &quot;&quot;&quot;\r\n\r\n    # add byte if not dividable by 2\r\n    if len(packet) &amp; 1:              \r\n        packet = packet + '&#92;&#48;'\r\n\r\n    # split into 16-bit word and insert into a binary array\r\n    words = array.array('h', packet) \r\n    sum = 0\r\n\r\n    # perform ones complement arithmetic on 16-bit words\r\n    for word in words:\r\n        sum += (word &amp; 0xffff) \r\n\r\n    hi = sum &gt;&gt; 16 \r\n    lo = sum &amp; 0xffff \r\n    sum = hi + lo\r\n    sum = sum + (sum &gt;&gt; 16)\r\n    \r\n    return (~sum) &amp; 0xffff # return ones complement\r\n\r\ndef pingNode(alive=0, timeout=1.0, ipv6=0, number=sys.maxint, node=None, \\\r\n             flood=0, size=ICMP_DATA_STR):\r\n    &quot;&quot;&quot;Pings a node based on input given to the function.\r\n    &quot;&quot;&quot;\r\n\r\n    # if no node, exit\r\n    if not node:\r\n        _error(&quot;&quot;)\r\n\r\n    # if not a valid host, exit\r\n    if ipv6:\r\n        if socket.has_ipv6:\r\n            try:\r\n                info, port = socket.getaddrinfo(node, None)\r\n                host = info&#x5B;4]&#x5B;0]\r\n                # do not print ipv6 twice if ipv6 address given as node\r\n                if host == node: \r\n                    noPrintIPv6adr = 1\r\n            except:\r\n                _error(&quot;cannot resolve %s: Unknow host&quot; % node)\r\n        else:\r\n            _error(&quot;No support for IPv6 on this plattform&quot;)\r\n    else:    # IPv4\r\n        try:\r\n            host = socket.gethostbyname(node)\r\n        except:\r\n            _error(&quot;cannot resolve %s: Unknow host&quot; % node)\r\n\r\n    # trying to ping a network?\r\n    if not ipv6:\r\n        if int(string.split(host, &quot;.&quot;)&#x5B;-1]) == 0:\r\n            _error(&quot;no support for network ping&quot;)\r\n\r\n    # do some sanity check\r\n    if number == 0:\r\n        _error(&quot;invalid count of packets to transmit: '%s'&quot; % str(a))\r\n    if alive:\r\n        number = 1\r\n\r\n    # Send the ping(s)\r\n    start = 1; mint = 999; maxt = 0.0; avg = 0.0\r\n    lost = 0; tsum = 0.0; tsumsq = 0.0\r\n\r\n    # tell the user what we do\r\n    if not alive:\r\n        if ipv6:\r\n            # do not print the ipv6 twice if ip adress given as node\r\n            # (it can be to long in term window)\r\n            if noPrintIPv6adr == 1:\r\n                # add 40 (header) + 8 (icmp header) + payload\r\n                print &quot;PING %s : %d data bytes (40+8+%d)&quot; % (str(node), \\\r\n                                                             40+8+size, size)\r\n            else:\r\n                # add 40 (header) + 8 (icmp header) + payload\r\n                print &quot;PING %s (%s): %d data bytes (40+8+%d)&quot; % (str(node), \\\r\n                                                                 str(host), 40+8+size, size)\r\n        else:\r\n            # add 20 (header) + 8 (icmp header) + payload\r\n            print &quot;PING %s (%s): %d data bytes (20+8+%d)&quot; % (str(node), str(host), \\\r\n                                                             20+8+size, size)\r\n        \r\n    # trap ctrl-d and ctrl-c\r\n    try:\r\n        \r\n        # send the number of ping packets as given\r\n        while start &lt; = number:\r\n            lost += 1 # in case user hit ctrl-c\r\n            \r\n            # create the IPv6\/IPv4 socket\r\n            if ipv6:\r\n                # can not create a raw socket if not root or setuid to root\r\n                try:\r\n                    pingSocket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, \\\r\n                                               socket.getprotobyname(&quot;ipv6-icmp&quot;))\r\n                except socket.error, e:\r\n                    print &quot;socket error: %s&quot; % e\r\n                    _error(&quot;You must be root (uses raw sockets)&quot; % os.path.basename(sys.argv&#x5B;0]))\r\n                    \r\n            # IPv4\r\n            else:\r\n                # can not create a raw socket if not root or setuid to root\r\n                try:\r\n                    pingSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, \\\r\n                                               socket.getprotobyname(&quot;icmp&quot;))\r\n                except socket.error, e:\r\n                    print &quot;socket error: %s&quot; % e\r\n                    _error(&quot;You must be root (%s uses raw sockets)&quot; % os.path.basename(sys.argv&#x5B;0]))\r\n                \r\n            packet = _construct(start, size, ipv6) # make a ping packet\r\n\r\n            # send the ping\r\n            try:\r\n                pingSocket.sendto(packet,(node,1))\r\n            except socket.error, e:\r\n                _error(&quot;socket error: %s&quot; % e)\r\n\r\n            # reset values\r\n            pong = &quot;&quot;; iwtd = &#x5B;]\r\n\r\n            # wait until there is data in the socket\r\n            while 1:\r\n                # input, output, exceptional conditions\r\n                iwtd, owtd, ewtd = select.select(&#x5B;pingSocket], &#x5B;], &#x5B;], timeout)\r\n                break # no data and timout occurred \r\n\r\n            # data on socket - this means we have an answer\r\n            if iwtd:  # ok, data on socket\r\n                endtime = time.time()  # time packet received\r\n                # read data (we only need the header)\r\n                pong, address = pingSocket.recvfrom(size+48)\r\n                lost -= 1 # in case user hit ctrl-c\r\n\r\n                # examine packet\r\n                # fetch TTL from IP header\r\n                if ipv6:\r\n                    # since IPv6 header and any extension header are never passed\r\n                    # to a raw socket, we can *not* get hoplimit field..\r\n                    # I hoped that a socket option would help, but it's not\r\n                    # supported:\r\n                    #   pingSocket.setsockopt(IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1)\r\n                    # so we can't fetch hoplimit..\r\n\r\n                    # fetch hoplimit\r\n                    #rawPongHop = struct.unpack(&quot;c&quot;, pong&#x5B;7])&#x5B;0]\r\n\r\n                    # fetch pong header\r\n                    pongHeader = pong&#x5B;0:8]\r\n                    pongType, pongCode, pongChksum, pongID, pongSeqnr = \\\r\n                              struct.unpack(&quot;bbHHh&quot;, pongHeader)\r\n\r\n                    # fetch starttime from pong\r\n                    starttime = struct.unpack(&quot;d&quot;, pong&#x5B;8:16])&#x5B;0]\r\n\r\n                # IPv4\r\n                else:\r\n                    # time to live\r\n                    rawPongHop = struct.unpack(&quot;s&quot;, pong&#x5B;8])&#x5B;0]\r\n\r\n                    # convert TTL from 8 bit to 16 bit integer\r\n                    pongHop = int(binascii.hexlify(str(rawPongHop)), 16)\r\n\r\n                    # fetch pong header\r\n                    pongHeader = pong&#x5B;20:28]\r\n                    pongType, pongCode, pongChksum, pongID, pongSeqnr = \\\r\n                              struct.unpack(&quot;bbHHh&quot;, pongHeader)\r\n\r\n                    # fetch starttime from pong\r\n                    starttime = struct.unpack(&quot;d&quot;, pong&#x5B;28:36])&#x5B;0]\r\n\r\n                # valid ping packet received?\r\n                if not pongSeqnr == start:\r\n                    pong = None\r\n\r\n            # NO data on socket - timeout waiting for answer\r\n            if not pong:\r\n                if alive:\r\n                    print &quot;no reply from %s (%s)&quot; % (str(node), str(host))\r\n                else:\r\n                    print &quot;ping timeout: %s (icmp_seq=%d) &quot; % (host, start)\r\n\r\n                # do not wait if just sending one packet\r\n                if number != 1 and start &lt; number:\r\n                    time.sleep(flood ^ 1)\r\n                start += 1\r\n                continue  # lost a packet - try again\r\n\r\n            triptime  = endtime - starttime # compute RRT\r\n            tsum     += triptime            # triptime for all packets (stddev)\r\n            tsumsq   += triptime * triptime # triptime^2  for all packets (stddev)\r\n\r\n            # compute statistic\r\n            maxt = max ((triptime, maxt))\r\n            mint = min ((triptime, mint))\r\n\r\n            if alive:\r\n                print str(node) + &quot; (&quot; + str(host) +&quot;) is alive&quot;\r\n            else:\r\n                if ipv6:\r\n                    # size + 8 = payload + header\r\n                    print &quot;%d bytes from %s: icmp_seq=%d time=%.5f ms&quot; % \\\r\n                          (size+8, host, pongSeqnr, triptime*1000)\r\n                else:\r\n                    print &quot;%d bytes from %s: icmp_seq=%d ttl=%s time=%.5f ms&quot; % \\\r\n                          (size+8, host, pongSeqnr, pongHop, triptime*1000)\r\n\r\n            # do not wait if just sending one packet\r\n            if number != 1 and start &lt; number:\r\n                # if flood = 1; do not sleep - just ping                \r\n                time.sleep(flood ^ 1) # wait before send new packet\r\n\r\n            # the last thing to do is update the counter - else the value\r\n            # (can) get wrong when computing summary at the end (if user\r\n            # hit ctrl-c when pinging)\r\n            start += 1\r\n            # end ping send\/recv while\r\n\r\n    # if user ctrl-d or ctrl-c\r\n    except (EOFError, KeyboardInterrupt):\r\n        # if user disrupts ping, it is most likly done before\r\n        # the counter get updates - if do not update it here, the\r\n        # summary get all wrong.\r\n        start += 1\r\n        pass\r\n\r\n    # compute and print som stats\r\n    # stddev computation based on ping.c from FreeBSD\r\n    if start != 0 or lost &gt; 0:  # do not print stats if 0 packet sent\r\n        start -= 1              # since while is '&lt; ='\r\n        avg = tsum \/ start      # avg round trip\r\n        vari = tsumsq \/ start - avg * avg \r\n        # %-packet lost\r\n        if start == lost:\r\n            plost = 100\r\n        else:\r\n            plost = (lost\/start)*100\r\n\r\n        if not alive:\r\n            print &quot;\\n--- %s ping statistics ---&quot; % node\r\n            print &quot;%d packets transmitted, %d packets received, %d%% packet loss&quot; % \\\r\n                  (start, start-lost, plost)\r\n            # don't display summary if 100% packet-loss\r\n            if plost != 100:\r\n                print &quot;round-trip min\/avg\/max\/stddev = %.3f\/%.3f\/%.3f\/%.3f ms&quot; % \\\r\n                      (mint*1000, (tsum\/start)*1000, maxt*1000, math.sqrt(vari)*1000)\r\n\r\n    pingSocket.close()\r\n    \r\ndef _error(err):\r\n    &quot;&quot;&quot;Exit if running standalone, else raise an exception\r\n    &quot;&quot;&quot;\r\n\r\n    if __name__ == '__main__':\r\n        print &quot;%s: %s&quot; % (os.path.basename(sys.argv&#x5B;0]), str(err))\r\n        print &quot;Try `%s --help' for more information.&quot; % os.path.basename(sys.argv&#x5B;0])\r\n        sys.exit(1)\r\n    else:\r\n        raise Exception, str(err)\r\n    \r\ndef _usage():\r\n    &quot;&quot;&quot;Print usage if run as a standalone program\r\n    &quot;&quot;&quot;\r\n    print &quot;&quot;&quot;usage: %s &#x5B;OPTIONS] HOST\r\nSend ICMP ECHO_REQUEST packets to network hosts.\r\n\r\nMandatory arguments to long options are mandatory for short options too.\r\n  -c, --count=N    Stop after sending (and receiving) 'N' ECHO_RESPONSE\r\n                   packets.\r\n  -s, --size=S     Specify the number of data bytes to be sent. The default\r\n                   is 56, which translates into 64 ICMP data bytes when\r\n                   combined with the 8 bytes of ICMP header data.\r\n  -f, --flood      Flood ping. Outputs packets as fast as they come back. Use\r\n                   with caution!\r\n  -6, --ipv6       Ping using IPv6.\r\n  -t, --timeout=s  Specify a timeout, in seconds, before a ping packet is\r\n                   considered 'lost'.\r\n  -h, --help       Display this help and exit\r\n\r\nReport bugs to lars &#x5B;at] gnist org&quot;&quot;&quot; % os.path.basename(sys.argv&#x5B;0])\r\n\r\n\r\nif __name__ == '__main__':\r\n    &quot;&quot;&quot;Main loop\r\n    &quot;&quot;&quot;\r\n\r\n    # version control\r\n    version = string.split(string.split(sys.version)&#x5B;0]&#x5B;:3], &quot;.&quot;)\r\n    if map(int, version) &lt; &#x5B;2, 3]:\r\n        _error(&quot;You need Python ver 2.3 or higher to run!&quot;)\r\n\r\n    try:\r\n        # opts = arguments recognized,\r\n        # args = arguments NOT recognized (leftovers)\r\n        opts, args = getopt.getopt(sys.argv&#x5B;1:-1], &quot;hat:6c:fs:&quot;, \\\r\n                                   &#x5B;&quot;help&quot;, &quot;alive&quot;, &quot;timeout=&quot;, &quot;ipv6&quot;, \\\r\n                                    &quot;count=&quot;, &quot;flood&quot;, &quot;packetsize=&quot;])\r\n    except getopt.GetoptError:\r\n        # print help information and exit:\r\n        _error(&quot;illegal option(s) -- &quot; + str(sys.argv&#x5B;1:]))\r\n\r\n    # test whether any host given\r\n    if len(sys.argv) &gt;= 2:\r\n        node = sys.argv&#x5B;-1:]&#x5B;0]   # host to be pinged\r\n        if node&#x5B;0] == '-' or node == '-h' or node == '--help' :  \r\n            _usage()\r\n    else:\r\n        _error(&quot;No arguments given&quot;)\r\n\r\n    if args:\r\n        _error(&quot;illegal option -- %s&quot; % str(args))\r\n        \r\n    # default variables\r\n    alive = 0; timeout = 1.0; ipv6 = 0; count = sys.maxint;\r\n    flood = 0; size = ICMP_DATA_STR\r\n\r\n    # run through arguments and set variables\r\n    for o, a in opts:\r\n        if o == &quot;-h&quot; or o == &quot;--help&quot;:    # display help and exit\r\n            _usage()\r\n            sys.exit(0)\r\n        if o == &quot;-t&quot; or o == &quot;--timeout&quot;: # timeout before &quot;lost&quot;\r\n            try:\r\n                timeout = float(a)\r\n            except:\r\n                _error(&quot;invalid timout: '%s'&quot; % str(a))\r\n        if o == &quot;-6&quot; or o == &quot;--ipv6&quot;:    # ping ipv6\r\n            ipv6 = 1\r\n        if o == &quot;-c&quot; or o == &quot;--count&quot;:   # how many pings?\r\n            try:\r\n                count = int(a)\r\n            except:\r\n                _error(&quot;invalid count of packets to transmit: '%s'&quot; % str(a))\r\n        if o == &quot;-f&quot; or o == &quot;--flood&quot;:   # no delay between ping send\r\n            flood = 1\r\n        if o == &quot;-s&quot; or o == &quot;--packetsize&quot;:  # set the ping payload size\r\n            try:\r\n                size = int(a)\r\n            except:\r\n                _error(&quot;invalid packet size: '%s'&quot; % str(a))\r\n        # just send one packet and say &quot;it's alive&quot;\r\n        if o == &quot;-a&quot; or o == &quot;--alive&quot;:   \r\n            alive = 1\r\n\r\n    # here we send\r\n    pingNode(alive=alive, timeout=timeout, ipv6=ipv6, number=count, \\\r\n             node=node, flood=flood, size=size)\r\n    # if we made it this far, do a clean exit\r\n    sys.exit(0)\r\n\r\n### end\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>#!\/usr\/bin\/env python # -*- coding: iso-8859-1 -*- &quot;&quot;&quot;ping.py ping.py uses the ICMP protocol&#8217;s mandatory ECHO_REQUEST datagram to elicit an ICMP ECHO_RESPONSE from a host or gateway. Copyright (C) 2004 &#8211; Lars Strand (lars strand at gnist org) This program is [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[30],"class_list":["post-20","post","type-post","status-publish","format-standard","hentry","category-tech","tag-python"],"_links":{"self":[{"href":"https:\/\/minqiao.me\/index.php?rest_route=\/wp\/v2\/posts\/20","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/minqiao.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/minqiao.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/minqiao.me\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/minqiao.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=20"}],"version-history":[{"count":2,"href":"https:\/\/minqiao.me\/index.php?rest_route=\/wp\/v2\/posts\/20\/revisions"}],"predecessor-version":[{"id":429,"href":"https:\/\/minqiao.me\/index.php?rest_route=\/wp\/v2\/posts\/20\/revisions\/429"}],"wp:attachment":[{"href":"https:\/\/minqiao.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=20"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/minqiao.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=20"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/minqiao.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=20"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}