{"action":"created","comment":{"url":"https://api.github.com/repos/mirage/mirage-www/pulls/comments/47429638","id":47429638,"diff_hunk":"@@ -0,0 +1,221 @@\n+Almost every network needs to support [DHCP]\n+(https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) (Dynamic\n+Host Configuration Protocol), that is, a way for clients to request network\n+parameters from the environment. Common parameters are an IP address, a network\n+mask, a default gateway and so on.\n+\n+DHCP can be seen as a critical security component, since it deals usually with\n+unauthenticated/unknown peers, therefore it is of special interest to run a\n+server as a self-contained MirageOS VM.\n+\n+[Charrua](http://www.github.com/haesbaert/charrua-core) is a DHCP implementation\n+written in OCaml, it started off as an excuse to learn more about the language,\n+while in development it got picked up on the MirageOS mailing lists and became one\n+of the [Pioneer\n+Projects](https://github.com/mirage/mirage-www/wiki/Pioneer-Projects).\n+\n+The name `Charrua` is a reference to the, now extinct, semi-nomadic people of\n+southern South America, nowadays it is also used to refer to Uruguayan\n+nationals. The logic is that DHCP handles dynamic (hence nomadic) clients.\n+\n+The library is platform agnostic and works outside of MirageOS as well, it\n+provides two main modules:\n+[Dhcp_wire](http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html) and\n+[Dhcp_server](http://haesbaert.github.io/charrua-core/api/Dhcp_server.html).\n+\n+### Dhcp_wire\n+\n+[Dhcp_wire](http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html) provides\n+basic functions for dealing with the protocol, essentialy\n+marshalling/unmarshalling and helpers for dealing with the various DHCP options.\n+\n+The central record type of\n+[Dhcp_wire](http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html) is a\n+[pkt](http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html#TYPEpkt), which\n+represents a full DHCP packet, including layer 2 and layer 3 data as well as the\n+many possible DHCP options. The most important functions are:\n+\n+```ocaml\n+val pkt_of_buf : Cstruct.t -> int -> [> `Error of string | `Ok of pkt ]\n+val buf_of_pkt : pkt -> Cstruct.t\n+```\n+\n+[pkt_of_buf]\n+(http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html#VALpkt_of_buf) takes\n+a [Cstruct.t] (https://github.com/mirage/ocaml-cstruct) buffer and a length, it\n+then attempts to build a DHCP packet, unknown DHCP options are ignored, invalid\n+options or malformed data is not accepted and you get a `` `Error of string``.\n+\n+[buf_of_pkt]\n+(http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html#VALbuf_of_pkt) is\n+the mirror function, but it never fails, it could for instance fail in case of\n+two duplicate DHCP options, but that would imply too much policy in a\n+marshalling function.\n+\n+The DHCP options from RFC2132 are implemented in\n+[dhcp_option](http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html#TYPEdhcp_option),\n+there are more, but the most common ones look like this:\n+\n+```ocaml\n+type dhcp_option =\n+  | Subnet_mask of Ipaddr.V4.t\n+  | Time_offset of int32\n+  | Routers of Ipaddr.V4.t list\n+  | Time_servers of Ipaddr.V4.t list\n+  | Name_servers of Ipaddr.V4.t list\n+  | Dns_servers of Ipaddr.V4.t list\n+  | Log_servers of Ipaddr.V4.t list\n+```\n+\n+### Dhcp_server\n+\n+[Dhcp_server](http://haesbaert.github.io/charrua-core/api/Dhcp_server.html)\n+Provides a library for building DHCP server, it is divided into two sub-modules:\n+[Config](http://haesbaert.github.io/charrua-core/api/Dhcp_server.Config.html),\n+which handles the building of a suitable DHCP server configuration record and\n+[Input](http://haesbaert.github.io/charrua-core/api/Dhcp_server.Config.html),\n+which handles the input of DHCP packets.\n+\n+The logic is modeled in a pure functional style,\n+[Dhcp_server](http://haesbaert.github.io/charrua-core/api/Dhcp_server.html) does\n+not perform any IO of its own, it works by taking an input\n+[packet](http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html#TYPEpkt),\n+ a\n+ [configuration](http://haesbaert.github.io/charrua-core/api/Dhcp_server.Config.html#TYPEt)\n+ and returns a possible reply to be sent by the caller, or an error/warning:\n+\n+#### Input\n+\n+```ocaml\n+type result = \n+| Silence                 (* Input packet didn't belong to us, normal nop event. *)\n+| Reply of Dhcp_wire.pkt  (* A reply packet to be sent on the same subnet. *)\n+| Warning of string       (* An odd event, could be logged. *)\n+| Error of string         (* Input packet is invalid, or some other error ocurred. *)\n+\n+val input_pkt : Dhcp_server.Config.t ->  Dhcp_server.Config.subnet ->\n+   Dhcp_wire.pkt -> float -> result\n+(** input_pkt config subnet pkt time Inputs packet pkt, the resulting action\n+    should be performed by the caller, normally a Reply packet is returned and\n+    must be sent on the same subnet. time is a float representing time as in\n+    Unix.time or MirageOS's Clock.time. **)\n+```\n+\n+A typical main server loop would work by:\n+ 1. Reading a packet from the network.\n+ 2. Unmarshaling with [Dhcp_wire.pkt_of_buf]\n+(http://haesbaert.github.io/charrua-core/api/Dhcp_wire.html#VALpkt_of_buf).\n+ 3. Inputing the result with [Dhcp_server.Input.input_pkt]\n+(http://haesbaert.github.io/charrua-core/api/Dhcp_server.Input.html#VALinput_pkt).\n+ 4. Sending the reply, or logging the event from the [Dhcp_server.Input.input_pkt]\n+(http://haesbaert.github.io/charrua-core/api/Dhcp_server.Input.html#VALinput_pkt) call.\n+\n+A mainloop example can be found in\n+[mirage-skeleton](https://github.com/mirage/mirage-skeleton/blob/master/dhcp/unikernel.ml#L28):\n+\n+```ocaml\n+  let input_dhcp c net config subnet buf =\n+    let open Dhcp_server.Input in\n+    match (Dhcp_wire.pkt_of_buf buf (Cstruct.len buf)) with\n+    | `Error e -> Lwt.return (log c (red \"Can't parse packet: %s\" e))\n+    | `Ok pkt ->\n+      match (input_pkt config subnet pkt (Clock.time ())) with\n+      | Silence -> Lwt.return_unit\n+      | Warning w -> Lwt.return (log c (yellow \"%s\" w))\n+      | Error e -> Lwt.return (log c (red \"%s\" e))\n+      | Reply reply ->\n+        log c (blue \"Received packet %s\" (Dhcp_wire.pkt_to_string pkt));\n+        N.write net (Dhcp_wire.buf_of_pkt reply)\n+        >>= fun () ->\n+        log c (blue \"Sent reply packet %s\" (Dhcp_wire.pkt_to_string reply));\n+        Lwt.return_unit\n+```\n+\n+As stated,\n+[Dhcp_server.Input.input_pkt](http://haesbaert.github.io/charrua-core/api/Dhcp_server.Input.html#VALinput_pkt)\n+does not perform any IO of its own, it deals only with the logic of inputing a\n+DHCP packet and returning a possible answer, which should then be sent by the\n+caller. This allows a design where all the side effects are controlled in one\n+small chunk, it makes it easier to understand the state transitions when they\n+are made explicit.\n+\n+At the time of this writing,\n+[Dhcp_server.Input.input_pkt](http://haesbaert.github.io/charrua-core/api/Dhcp_server.Input.html#VALinput_pkt)\n+is not side effect free, as it manipulates a database of leases, this will be\n+changed in the next version to be pure as well.\n+\n+Storing leases in permanent storage is also not supported at this time and\n+should be available soon, with irmin and other backends. The main idea is to\n+always return a new lease database at every input, or maybe just the updates to\n+be applied, in this scenario, the caller would be able to store the database in\n+permanent storage as he sees fit.\n+\n+#### Configuration\n+\n+This project started independently of MirageOS, and at that time, the best\n+configuration I could think of was the well known `ISC` `dhcpd.conf`, therefore\n+the configuration uses the same format, but it does not support all the myriad\n+of options the original one does.\n+\n+```ocaml\n+  type t = {\n+    addresses : (Ipaddr.V4.t * Macaddr.t) list;\n+    subnets : subnet list;\n+    options : Dhcp_wire.dhcp_option list;\n+    hostname : string;\n+    default_lease_time : int32;\n+    max_lease_time : int32;\n+  }\n+\n+  val parse : string -> (Ipaddr.V4.Prefix.addr * Macaddr.t) list -> t\n+  (** [parse cf l] Creates a server configuration by parsing [cf] as an ISC\n+      dhcpd.conf file, currently only the options at [sample/dhcpd.conf] are\n+      supported. [l] is a list of network addresses, each pair is the output\n+      address to be used for building replies and each must match a [network\n+      section] of [cf]. A normal usage would be a list of all interfaces\n+      configured in the system *)\n+```\n+\n+Although it is a great format, it doesn't play exactly nice with MirageOS and\n+OCaml, since the unikernel needs to parse a string at runtime to build the\n+configuration, this requires a file IO backend and other complications. The\n+next version should provide OCaml helpers for building the configuration, which\n+would drop the requirements of a file IO backend and facilitate writing tests.\n+\n+### Building a simple server\n+\n+The easiest way is to follow [mirage-skeleton DHCP\n+README](https://github.com/mirage/mirage-skeleton/blob/master/dhcp/README.md).\n+","path":"tmpl/blog/introducing-charrua-dhcp.md","position":189,"original_position":189,"commit_id":"eef78a645a7590e5dc1d0bf6fc750e19869584f2","original_commit_id":"eef78a645a7590e5dc1d0bf6fc750e19869584f2","user":{"login":"Chris00","id":1255665,"avatar_url":"https://avatars.githubusercontent.com/u/1255665?v=3","gravatar_id":"","url":"https://api.github.com/users/Chris00","html_url":"https://github.com/Chris00","followers_url":"https://api.github.com/users/Chris00/followers","following_url":"https://api.github.com/users/Chris00/following{/other_user}","gists_url":"https://api.github.com/users/Chris00/gists{/gist_id}","starred_url":"https://api.github.com/users/Chris00/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Chris00/subscriptions","organizations_url":"https://api.github.com/users/Chris00/orgs","repos_url":"https://api.github.com/users/Chris00/repos","events_url":"https://api.github.com/users/Chris00/events{/privacy}","received_events_url":"https://api.github.com/users/Chris00/received_events","type":"User","site_admin":false},"body":"I was expecting this post to give some comparison with existing DHCP implementations — for example in terms of speed, using some [stress](https://github.com/dyninc/DHCPTest-python) [test](http://vigneshmurugesan.com/2013/04/14/running-dhcperf-on-ubuntu/), or in terms of functionality (are there tweaks needed for some clients?).  I do not speak authoritatively but IMHO that would be a nice addition.","created_at":"2015-12-12T09:14:13Z","updated_at":"2015-12-12T09:14:13Z","html_url":"https://github.com/mirage/mirage-www/pull/414#discussion_r47429638","pull_request_url":"https://api.github.com/repos/mirage/mirage-www/pulls/414","_links":{"self":{"href":"https://api.github.com/repos/mirage/mirage-www/pulls/comments/47429638"},"html":{"href":"https://github.com/mirage/mirage-www/pull/414#discussion_r47429638"},"pull_request":{"href":"https://api.github.com/repos/mirage/mirage-www/pulls/414"}}},"pull_request":{"url":"https://api.github.com/repos/mirage/mirage-www/pulls/414","id":52007054,"html_url":"https://github.com/mirage/mirage-www/pull/414","diff_url":"https://github.com/mirage/mirage-www/pull/414.diff","patch_url":"https://github.com/mirage/mirage-www/pull/414.patch","issue_url":"https://api.github.com/repos/mirage/mirage-www/issues/414","number":414,"state":"open","locked":false,"title":"Add a blog entry for Charrua DHCP implementation.","user":{"login":"haesbaert","id":204048,"avatar_url":"https://avatars.githubusercontent.com/u/204048?v=3","gravatar_id":"","url":"https://api.github.com/users/haesbaert","html_url":"https://github.com/haesbaert","followers_url":"https://api.github.com/users/haesbaert/followers","following_url":"https://api.github.com/users/haesbaert/following{/other_user}","gists_url":"https://api.github.com/users/haesbaert/gists{/gist_id}","starred_url":"https://api.github.com/users/haesbaert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/haesbaert/subscriptions","organizations_url":"https://api.github.com/users/haesbaert/orgs","repos_url":"https://api.github.com/users/haesbaert/repos","events_url":"https://api.github.com/users/haesbaert/events{/privacy}","received_events_url":"https://api.github.com/users/haesbaert/received_events","type":"User","site_admin":false},"body":"Initial stab as requested.","created_at":"2015-11-27T15:46:23Z","updated_at":"2015-12-12T09:14:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b4bd70d356a938672c27fec25e204a90fcfd4850","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mirage/mirage-www/pulls/414/commits","review_comments_url":"https://api.github.com/repos/mirage/mirage-www/pulls/414/comments","review_comment_url":"https://api.github.com/repos/mirage/mirage-www/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mirage/mirage-www/issues/414/comments","statuses_url":"https://api.github.com/repos/mirage/mirage-www/statuses/eef78a645a7590e5dc1d0bf6fc750e19869584f2","head":{"label":"haesbaert:master","ref":"master","sha":"eef78a645a7590e5dc1d0bf6fc750e19869584f2","user":{"login":"haesbaert","id":204048,"avatar_url":"https://avatars.githubusercontent.com/u/204048?v=3","gravatar_id":"","url":"https://api.github.com/users/haesbaert","html_url":"https://github.com/haesbaert","followers_url":"https://api.github.com/users/haesbaert/followers","following_url":"https://api.github.com/users/haesbaert/following{/other_user}","gists_url":"https://api.github.com/users/haesbaert/gists{/gist_id}","starred_url":"https://api.github.com/users/haesbaert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/haesbaert/subscriptions","organizations_url":"https://api.github.com/users/haesbaert/orgs","repos_url":"https://api.github.com/users/haesbaert/repos","events_url":"https://api.github.com/users/haesbaert/events{/privacy}","received_events_url":"https://api.github.com/users/haesbaert/received_events","type":"User","site_admin":false},"repo":{"id":44436091,"name":"mirage-www","full_name":"haesbaert/mirage-www","owner":{"login":"haesbaert","id":204048,"avatar_url":"https://avatars.githubusercontent.com/u/204048?v=3","gravatar_id":"","url":"https://api.github.com/users/haesbaert","html_url":"https://github.com/haesbaert","followers_url":"https://api.github.com/users/haesbaert/followers","following_url":"https://api.github.com/users/haesbaert/following{/other_user}","gists_url":"https://api.github.com/users/haesbaert/gists{/gist_id}","starred_url":"https://api.github.com/users/haesbaert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/haesbaert/subscriptions","organizations_url":"https://api.github.com/users/haesbaert/orgs","repos_url":"https://api.github.com/users/haesbaert/repos","events_url":"https://api.github.com/users/haesbaert/events{/privacy}","received_events_url":"https://api.github.com/users/haesbaert/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/haesbaert/mirage-www","description":"Website infrastructure for mirage.io","fork":true,"url":"https://api.github.com/repos/haesbaert/mirage-www","forks_url":"https://api.github.com/repos/haesbaert/mirage-www/forks","keys_url":"https://api.github.com/repos/haesbaert/mirage-www/keys{/key_id}","collaborators_url":"https://api.github.com/repos/haesbaert/mirage-www/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/haesbaert/mirage-www/teams","hooks_url":"https://api.github.com/repos/haesbaert/mirage-www/hooks","issue_events_url":"https://api.github.com/repos/haesbaert/mirage-www/issues/events{/number}","events_url":"https://api.github.com/repos/haesbaert/mirage-www/events","assignees_url":"https://api.github.com/repos/haesbaert/mirage-www/assignees{/user}","branches_url":"https://api.github.com/repos/haesbaert/mirage-www/branches{/branch}","tags_url":"https://api.github.com/repos/haesbaert/mirage-www/tags","blobs_url":"https://api.github.com/repos/haesbaert/mirage-www/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/haesbaert/mirage-www/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/haesbaert/mirage-www/git/refs{/sha}","trees_url":"https://api.github.com/repos/haesbaert/mirage-www/git/trees{/sha}","statuses_url":"https://api.github.com/repos/haesbaert/mirage-www/statuses/{sha}","languages_url":"https://api.github.com/repos/haesbaert/mirage-www/languages","stargazers_url":"https://api.github.com/repos/haesbaert/mirage-www/stargazers","contributors_url":"https://api.github.com/repos/haesbaert/mirage-www/contributors","subscribers_url":"https://api.github.com/repos/haesbaert/mirage-www/subscribers","subscription_url":"https://api.github.com/repos/haesbaert/mirage-www/subscription","commits_url":"https://api.github.com/repos/haesbaert/mirage-www/commits{/sha}","git_commits_url":"https://api.github.com/repos/haesbaert/mirage-www/git/commits{/sha}","comments_url":"https://api.github.com/repos/haesbaert/mirage-www/comments{/number}","issue_comment_url":"https://api.github.com/repos/haesbaert/mirage-www/issues/comments{/number}","contents_url":"https://api.github.com/repos/haesbaert/mirage-www/contents/{+path}","compare_url":"https://api.github.com/repos/haesbaert/mirage-www/compare/{base}...{head}","merges_url":"https://api.github.com/repos/haesbaert/mirage-www/merges","archive_url":"https://api.github.com/repos/haesbaert/mirage-www/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/haesbaert/mirage-www/downloads","issues_url":"https://api.github.com/repos/haesbaert/mirage-www/issues{/number}","pulls_url":"https://api.github.com/repos/haesbaert/mirage-www/pulls{/number}","milestones_url":"https://api.github.com/repos/haesbaert/mirage-www/milestones{/number}","notifications_url":"https://api.github.com/repos/haesbaert/mirage-www/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/haesbaert/mirage-www/labels{/name}","releases_url":"https://api.github.com/repos/haesbaert/mirage-www/releases{/id}","created_at":"2015-10-17T12:24:34Z","updated_at":"2015-10-17T12:24:36Z","pushed_at":"2015-12-11T12:22:10Z","git_url":"git://github.com/haesbaert/mirage-www.git","ssh_url":"git@github.com:haesbaert/mirage-www.git","clone_url":"https://github.com/haesbaert/mirage-www.git","svn_url":"https://github.com/haesbaert/mirage-www","homepage":"https://mirage.io","size":5191,"stargazers_count":0,"watchers_count":0,"language":"OCaml","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"mirage:master","ref":"master","sha":"28fc713a6245e5676c1ec587454e75e9859e9585","user":{"login":"mirage","id":131943,"avatar_url":"https://avatars.githubusercontent.com/u/131943?v=3","gravatar_id":"","url":"https://api.github.com/users/mirage","html_url":"https://github.com/mirage","followers_url":"https://api.github.com/users/mirage/followers","following_url":"https://api.github.com/users/mirage/following{/other_user}","gists_url":"https://api.github.com/users/mirage/gists{/gist_id}","starred_url":"https://api.github.com/users/mirage/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mirage/subscriptions","organizations_url":"https://api.github.com/users/mirage/orgs","repos_url":"https://api.github.com/users/mirage/repos","events_url":"https://api.github.com/users/mirage/events{/privacy}","received_events_url":"https://api.github.com/users/mirage/received_events","type":"Organization","site_admin":false},"repo":{"id":4755520,"name":"mirage-www","full_name":"mirage/mirage-www","owner":{"login":"mirage","id":131943,"avatar_url":"https://avatars.githubusercontent.com/u/131943?v=3","gravatar_id":"","url":"https://api.github.com/users/mirage","html_url":"https://github.com/mirage","followers_url":"https://api.github.com/users/mirage/followers","following_url":"https://api.github.com/users/mirage/following{/other_user}","gists_url":"https://api.github.com/users/mirage/gists{/gist_id}","starred_url":"https://api.github.com/users/mirage/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mirage/subscriptions","organizations_url":"https://api.github.com/users/mirage/orgs","repos_url":"https://api.github.com/users/mirage/repos","events_url":"https://api.github.com/users/mirage/events{/privacy}","received_events_url":"https://api.github.com/users/mirage/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mirage/mirage-www","description":"Website infrastructure for mirage.io","fork":false,"url":"https://api.github.com/repos/mirage/mirage-www","forks_url":"https://api.github.com/repos/mirage/mirage-www/forks","keys_url":"https://api.github.com/repos/mirage/mirage-www/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mirage/mirage-www/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mirage/mirage-www/teams","hooks_url":"https://api.github.com/repos/mirage/mirage-www/hooks","issue_events_url":"https://api.github.com/repos/mirage/mirage-www/issues/events{/number}","events_url":"https://api.github.com/repos/mirage/mirage-www/events","assignees_url":"https://api.github.com/repos/mirage/mirage-www/assignees{/user}","branches_url":"https://api.github.com/repos/mirage/mirage-www/branches{/branch}","tags_url":"https://api.github.com/repos/mirage/mirage-www/tags","blobs_url":"https://api.github.com/repos/mirage/mirage-www/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mirage/mirage-www/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mirage/mirage-www/git/refs{/sha}","trees_url":"https://api.github.com/repos/mirage/mirage-www/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mirage/mirage-www/statuses/{sha}","languages_url":"https://api.github.com/repos/mirage/mirage-www/languages","stargazers_url":"https://api.github.com/repos/mirage/mirage-www/stargazers","contributors_url":"https://api.github.com/repos/mirage/mirage-www/contributors","subscribers_url":"https://api.github.com/repos/mirage/mirage-www/subscribers","subscription_url":"https://api.github.com/repos/mirage/mirage-www/subscription","commits_url":"https://api.github.com/repos/mirage/mirage-www/commits{/sha}","git_commits_url":"https://api.github.com/repos/mirage/mirage-www/git/commits{/sha}","comments_url":"https://api.github.com/repos/mirage/mirage-www/comments{/number}","issue_comment_url":"https://api.github.com/repos/mirage/mirage-www/issues/comments{/number}","contents_url":"https://api.github.com/repos/mirage/mirage-www/contents/{+path}","compare_url":"https://api.github.com/repos/mirage/mirage-www/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mirage/mirage-www/merges","archive_url":"https://api.github.com/repos/mirage/mirage-www/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mirage/mirage-www/downloads","issues_url":"https://api.github.com/repos/mirage/mirage-www/issues{/number}","pulls_url":"https://api.github.com/repos/mirage/mirage-www/pulls{/number}","milestones_url":"https://api.github.com/repos/mirage/mirage-www/milestones{/number}","notifications_url":"https://api.github.com/repos/mirage/mirage-www/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mirage/mirage-www/labels{/name}","releases_url":"https://api.github.com/repos/mirage/mirage-www/releases{/id}","created_at":"2012-06-22T20:32:07Z","updated_at":"2015-11-28T12:07:15Z","pushed_at":"2015-12-11T12:22:11Z","git_url":"git://github.com/mirage/mirage-www.git","ssh_url":"git@github.com:mirage/mirage-www.git","clone_url":"https://github.com/mirage/mirage-www.git","svn_url":"https://github.com/mirage/mirage-www","homepage":"https://mirage.io","size":5348,"stargazers_count":75,"watchers_count":75,"language":"OCaml","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":53,"mirror_url":null,"open_issues_count":37,"forks":53,"open_issues":37,"watchers":75,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mirage/mirage-www/pulls/414"},"html":{"href":"https://github.com/mirage/mirage-www/pull/414"},"issue":{"href":"https://api.github.com/repos/mirage/mirage-www/issues/414"},"comments":{"href":"https://api.github.com/repos/mirage/mirage-www/issues/414/comments"},"review_comments":{"href":"https://api.github.com/repos/mirage/mirage-www/pulls/414/comments"},"review_comment":{"href":"https://api.github.com/repos/mirage/mirage-www/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mirage/mirage-www/pulls/414/commits"},"statuses":{"href":"https://api.github.com/repos/mirage/mirage-www/statuses/eef78a645a7590e5dc1d0bf6fc750e19869584f2"}}}}