~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INTERNET DRAFT Anant Kumar Expiration Date: April 12, 1994 Steve Hotz Jon Postel USC/ISI Oct. 1993 Incremental Transfer and Fast Convergence in DNS Status of this Memo This document is an Internet-Draft. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF), its Areas, and its Working Groups. Note that other groups may also distribute working documents as Internet-Drafts. Internet-Drafts are draft documents valid for a maximum of six months. Internet-Drafts may be updated, replaced, or obsoleted by other documents at any time. It is not appropriate to use Internet-Drafts as reference material or to cite them other than as a ``working draft'' or ``work in progress.'' To learn the current status of any Internet-Draft, please check the 1id-abstracts.txt listing contained in the Internet-Drafts Shadow Directories on ds.internic.net, nic.nordu.net, ftp.nisc.sri.com, or munnari.oz.au. This Internet Draft expires April 12, 1994. Abstract This memo proposes extensions to the DNS protocols to provide for an incremental zone transfer (IXFR) procedure. A companion mechanism, the NOTIFY procedure, is also proposed to allow secondaries to learn of changes to the primary database in a timely manner. Introduction The last few years have witnessed an exponential growth in the number of machines in the internet, and a corresponding dependence on DNS. As a result, zone files have grown to near HOSTS.TXT proportions. Each zone file is maintained at a primary server. All modifications to this file are made at the single site and propagated to secondary servers using the Zone Transfer protocol [RFC 1035]. Whenever any change is made to the zone file, the zone administrator increments the SOA serial number. Secondary servers poll the primary every REFRESH interval, and if the serial number has changed, the entire zone file is transferred. More often than not, the change Kumar, Hotz, Postel [Page 1] INTERNET DRAFT October, 1993 made to the zone file is a very small percentage of the zone file. Thus, an incremental transfer protocol that will propagate only the changes to the zone file, may allow substantial savings of bandwidth overhead. In addition, secondaries only check to see if they are consistent with the primary every REFRESH period. While setting REFRESH to be a relatively large value reduces bandwidth overhead, there can be large time intervals during which at least one secondary has data that is inconsistent with the primary. The proposed NOTIFY mechanism (where the primary sends a message to known secondaries) facilitates fast convergence of servers vis-a-vis consistency of data in the zones (without requiring the overhead implied by a short REFRESH period). These two mechanisms can be used to reduce the bandwidth overhead of DNS while maintaining server-to-server consistency for any particular zone. These mechanisms could prove particularly useful if a DNS of the future were required to support dynamic updates (e.g. frequent changes to a zone, possibly from multiple entities making changes by sending "update packets"). Dynamic updates imply small database changes, and a need for fast convergence among authoritative servers. This memo does not specifically address a Dynamic Update scheme, but the IXFR and NOTIFY mechanisms were designed in light of possible requirements for dynamic update schemes. The following changes to the DNS protocols are required to support the two new mechanisms: a new query type called IXFR is defined, an opcode called NOTIFY is defined, a new RR type called ISOA is defined, and two fields (a serial number and a flag) are added to each resource record. 1. Resource Record support for Incremental Transfers To support incremental zone transfers, an RR will now have 7 fields as follows: The current DNS mechanism does not provide a way to identify the chronological history of the data in the zone files. To support incremental transfers, it is necessary to know when a resource record was added to the database (relative to other updates). Thus, a serial number is associated with each resource record in a zone file. The "Zflag" (zombie flag) is necessary to convey incremental state information with respect to a resource record (i.e. should the RR be added or deleted from a secondaries zone information). Section 1.2 details the use of "Zflag". Kumar, Hotz, Postel [Page 2] INTERNET DRAFT October, 1993 For backward compatibility reasons, the serial number and "Zflag" will only be propagated along with IXFR transfers (further, a DNS client has no use for serial number information at this point). Future DNS clients might want to make use of this information, and new query types could be defined that would return serial numbers. 1.1 IXFR Use of RR Serial Numbers The RR serial numbers must be a strictly monotonically increasing function. This will allow servers to differentiate between two sets of RRs: those added before a certain serial number, and those added after a certain serial number. To illustrate the basic scheme, for the moment consider only the case of adding new RRs to a zone (the more subtle cases of deletion and modification are considered in detail below). When an RR is added to a zone, a new (higher) serial number is associated with the newly added RR. Because RR serial numbers are monotonically increasing, servers can distinguish when an RR was added (relative to other RRs). A scheme to conserve serial number space is described in section 1.4.1. The current status of zone information held at a particular server is reflected by the highest serial number associated with the RRs of the zone. When a secondary requests an incremental zone transfer (IXFR), it must send its current status (highest RR serial number) as part of this request. The primary server can then transfer all RR records that have a higher sequence number; consequently, the status of the zone information held at the primary and secondary will be the same. 1.2 Deleting/Modifying an existing resource Record A modification will be treated as a deletion followed by an addition, thus only the deletion process is described here. [NOTE: If there is a requirement for modification atomicity, this would require a distinct operation; this could be supported by extending the "zombie" mechanism described below.] When receiving IXFR updates, a secondary must receive an EXPLICIT notification of deleted RRs (unlike a full AXFR scheme where all RRs are considered deleted unless refreshed). Hence an RR cannot be removed from the primary zone when it is deleted; instead it is modified and used as the explicit notification (to the secondaries) of removal. The RR is marked as a "ZOMBIE" (using the Zflag) and the serial number is updated as described above. The zombie RR is kept in memory until the primary is sure all secondaries have updated zones to reflect the deleted RR. When the primary is certain all secondaries have received the notification of the deleted RR; during Kumar, Hotz, Postel [Page 3] INTERNET DRAFT October, 1993 the interim the primary, of course, does not return the deleted RR in response to client queries. No server should return a zombie RR in response to a client query. In a sense, an IXFR contains commands of two types: one that specifies a new RR should be added to the zone information, and one to delete an RR from the zone. To converge correctly, a server receiving an IXFR must apply/process these commands (RRs and zombie RRs) in order per the RR sequence numbers. Note that once a secondary applies a ZOMBIE RR to the zone information it holds, it does not need to maintain this ZOMBIE (unless it also serves to update other secondaries via IXFR). ZOMBIE RRs cannot be maintained indefinitely, because this would cause the amount of information maintained for the zone (at the primary) to be unnecessarily large (i.e. one does not want to maintain some number of ADD/DELETE pairs for a particular RR that could theoretically occur over time). Fortunately, there is no need to maintain ZOMBIE RRs indefinitely; they can be deleted when all servers for a zone have been notified of the deleted RR. 1.2.1 Mechanisms for Deleting "ZOMBIE" Resource Records There are multiple mechanisms that could be used to keep track of ZOMBIE RRs and when they can be deleted. Either or both of the following two schemes could be used, depending on the desired performance and overhead trade-offs. a. The primary can maintain information (state) about all secondaries that normally transfer zone from it. This will be "soft state", implying that it can be rebuilt from scratch should the primary server crash (the only impact being that ZOMBIE RRs may not be deleted as soon as they might otherwise). Hence, for each secondary server, the primary records the last serial number it transferred (on recovering from a crash, this number will be set to 0). When the minimum of these serial numbers (for all servers of a particular zone) is greater than the serial number on a ZOMBIE RR entry, that ZOMBIE RR can be removed. b. This scheme requires that secondary servers may sometime be "forced" to take an AXFR rather than an IXFR. A primary maintains all RRs within "N" serial numbers of the zone's current serial number (highest valued RR serial number); ZOMBIE RRs with serial numbers lower than (current_serial_number - N) are deleted. Any secondary server that requests a serial number smaller than the primary's (current_serial_number - N), must AXFR instead of IXFR. Kumar, Hotz, Postel [Page 4] INTERNET DRAFT October, 1993 The primary will send an AXFR reply (in response to the IXFR) and the secondary is expected to be able to parse either IXFR or AXFR responses to its original IXFR query. It should be built to parse it either way. This scheme is designed to be more efficient (and simpler) than the alternative where the primary refuses the IXFR and the following events happen: client: request(IXFR) server: refuse(IXFR) client: request(AXFR) server: send(AXFR) Either or both of these schemes, (a) and (b), could be used, and this can be an implementation-specific decision so long as the servers can interoperate. Scheme (a) requires no additional protocol interaction, but all [secondary] servers must accommodate a "you asked for an IXFR *but* here is an AXFR" if different implementations are to interoperate. While a primary could implement either approach, one should see an advantage by implementing both. The "soft state" method (a) will allow ZOMBIE RRs to be deleted as early as is possible, and the "refused IXFR" method (b) will place a bound on the amount of memory required by the primary (useful in the case where a secondary is out of touch for very long periods of time). 1.3 The Role of the SOA Serial Number Since IXFR-capable servers are likely to be used together with older servers during a transition period (of unfortunately indefinite length), the SOA serial number functionality must be preserved for backward compatibility. This implies that the SOA serial number must be changed each time the zone is updated. The simplest solution is for the SOA serial number to reflect the highest RR serial number. This issue is not difficult in the context of simply accommodating incremental zone transfers, since a zone file (and SOA serial number) will necessarily be updated when RRs are added, deleted, or modified. However, there is other ongoing work that is addressing mechanisms for dynamically updating zone information; it would be an advantage if the IXFR scheme considered such mechanisms, and was designed to accomodate the additional complexities introduced by dynamically updated zone information. If the use of dynamic updates are ignored, the SOA serial number could be updated in the same manner as it is today. In fact, the Kumar, Hotz, Postel [Page 5] INTERNET DRAFT October, 1993 manually-updated SOA serial number could be assigned to each new, modified, or deleted/zombie RR. Other implementation-specific schemes could be used to derive SOA serial numbers and maintain the relationship between SOA serial number and RR serial numbers. 1.3.1 SOA Serial Numbers in light of Dynamic Updates The IXFR scheme essentially obsoletes the function of the SOA serial number, replacing it with finer-granularity serial numbers applied to RRs (this is especially true if dynamically updates to the zone are made). The highest-valued RR serial number now reflects the status of the zone information. At this point, the question is unresolved as to how the SOA serial number should be treated. Without going into specifics, the crucial point is that zone information will not always change due to a zone file update. Further, RR serial numbers will be assigned dynamically which must be reflected in SOA serial number (for interoperability). An implication (which may be disturbing to the old guard of DNS administrators) is that SOA serial number will not be updated exclusively in the zone file. To preserve zone integrity, the changes made dynamically must take precedence over manually-updated SOA serial numbers; consider the case of a manually-updated SOA serial number being less than that required by the number of dynamic updates. Summary: The specifics of this issue can be resolved in separately from the dynamic update effort. However, the IXFR scheme should be aware that the use of the SOA serial number is likely to change as follows: (1) The function of the SOA serial number is replaced by the highest RR serial number. SOA serial number must also reflect changes to the zone for backward compatability; some mapping from RR serial numbers to SOA serial is required (the simplest being that they are the same). (2) A manual update of a zone file cannot specify an SOA serial number which conflicts with (is smaller than) SOA serial number that reflects dynamic changes to the zone. 1.4 New RR Serial Number Generation This is an implementation specific issue, as long as the the function is monotonically increasing, and the constraints imposed by the relationship between RR serial numbers and the SOA serial number are met (see Section 1.3). One obvious function is to maintain a Kumar, Hotz, Postel [Page 6] INTERNET DRAFT October, 1993 sequence number counter, which is updated each time a new RR (or ZOMBIE RR) is added (see section 1.4.1 for a variation of counter- based sequence number generation). A simple alternative (especially in light of dynamic updates) is to use a timestamp (seconds since the epoch) as determined at the primary server. The primary advantage (other than simplicity) is speculative; if the actual time of update is available to clients, the DNS system, or network administrators, it might serve some useful function in the future (e.g. perhaps network administrators can identify irregularities in a zone by examining RR serial number and applying heuristic that takes into account expected turn-over rate for the zone). Section 1.3 implies that manually-updated SOA serial numbers may not be possible in the future, hence the semantics attached to them by many administrators today (i.e. YYMMDDHH) may also be less clear. The use of timestamp-based serial number might be an appropriate replacement. The disadvantage of timestamps is that they might not be compatible with the existing serial number space. A future timestamp might be much smaller (numerically) than a serial number associated with a given zone today. This could, of course, be resolved by the rather drastic measure of shutting down all servers for a domain, deleting the zone backup files from all secondaries and starting afresh, with a new serial number space based on timestamps. 1.4.1 Reducing Sequence Number Rollover If a simple counter-based sequence number is used (rather than timestamp-based), the sequence number does not necessarily have to be updated each time a new RR is added. A scheme to conserve the use of serial number space could be based on whether other servers have received updates recently: If (zone transferred by anybody) set(NewNumNeeded); and when a new RR is added: If (NewNumNeeded) { currentserial++; reset(NewNumNeeded); } RR->serialNum = currentserial; Thus, all RRs added between any two transfers get the same serial Kumar, Hotz, Postel [Page 7] INTERNET DRAFT October, 1993 number, thereby saving some amount of serial number space. Note that when an RR is modified, this is actually a "delete" and an "add" which must be done in the correct order. If the ordering is based on sequence number, then the simple scheme above must accommodate this additional constraint. 2. ISOA, the new RR type. System administrators (for whatever reason) might not be comfortable depending exclusively on incremental transfer to maintain zone consistency. If this is the case, it can be resolved by configuring periodic "checkpoints" where full zone transfers are done. Thus, after REFRESH seconds, secondaries will use IXFR to transfer incremental data. In addition, secondaries will do a complete zone transfer every XXFRTIME seconds (typically at least an order of magnitude greater than REFRESH) using the method described later in this document (section 4). To specify the XXFRTIME, an additional RR type is defined (so we are backward compatible with existing DNS implementations). It has symbolic name "ISOA" and numeric value xxx. The data section of the ISOA record will look as follows: 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | XXFRTIME | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ This RR might, in the future, be expanded to contain other fields and we are considering giving it a more flexible structure to accommodate for those changes. 3. The Actual Mechanism An IXFR query packet will, necessarily, contain the highest RR serial number the secondary last saw. Thus, the DNS IXFR query packet will look like this. Kumar, Hotz, Postel [Page 8] INTERNET DRAFT October, 1993 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE = IXFR | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | SECONDARY SERIAL NUMBER | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ Note that QNAME, QTYPE and QCLASS are exactly as for any other query type. SECONDARY SERIAL NUMBER is the serial number the secondary must convey to the primary so the primary can send it all entries updated since that serial number was seen. 3.1 The Client Side The client (secondary) sends an SOA query to the server (primary) and compares the serial numbers. If (current > latest) { signal a possible error; QUIT; } If (current == latest) { exit gracefully; } If (current < latest) { transmit_IXFR_query(current_serial_number); destroy_zone_file(); receive_IXFR_response(&buf); update_zone_data(hashtab); recreate_zone_file(backup_zone_file); } Note that we destroy the zone file before we actually attempt to receive any data from the server. We do this since otherwise, if we receive data and crash before we are able to update our zone file, the primary will believe that we have the latest data while when we actually recover, we will not. The "recreate_zone_file()" operations needs to be atomic or at least, if the server crashes midway through the operation, it should be able to detect this when restarting. Kumar, Hotz, Postel [Page 9] INTERNET DRAFT October, 1993 Thus, by this destruction, we ensure that we either have the latest data or that we have nothing. This is of special importance here since if we do not destroy the zone file and the client crashes after it completes the transfer but before it can update the zone file, we have a problem. The server believes the client has the updated data (if it keeps soft state) while the client actually does not. Thus, when we actually recover from the crash, we initiate a full zone transfer from the server. 3.2 The Server The server follows this simple algorithm when processing IXFR queries. current_serial_from_client -> csn; Record (csn, soft_state_structure); If (csn == SOA_serial) { send blank response; } If (csn < SOA_serial - N) { refuse IXFR.. or send AXFR response; exit; } for (each entry in zone file) if(csn >= entry_serial_number) continue; else add entry to IXFR packet; endfor; Transmit IXFR packet to client. Thus, a server sends incremental transfers only if the csn from the client falls within N of the present value of the SOA serial number. It could either refuse a response if it falls beyond N in which case the client must be prepared to initiate an AXFR. Alternatively, the server could send the AXFR packet instead and the secondary must expect and be able to interpret that packet. 4. XXFR Once every XXFRTIME (section 2), the IXFR query packet will be: Kumar, Hotz, Postel [Page 10] INTERNET DRAFT October, 1993 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE = IXFR | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | SERIAL = 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ Note that the SECONDARY SERIAL NUMBER field equals the value "0". This prompts the server to send an IXFR packet that contains all the zone data, i.e. it is the equivalent of the AXFR. The system could use this to synchronize complete zone files at regular XXFRTIME intervals. 5. NOTIFY Currently, a secondary always waits "REFRESH" seconds before polling the primary for any changes in the zone. If a primary makes any changes (that may be rather important) and wants that all secondaries reflect these changes immediately, the primary has no means of talking to the secondaries. A mechanism must be available to notify the secondary that it might benefit from a zone transfer, right away if possible. We propose a new opcode, "NOTIFY", to fulfill exactly this need. When the database is updated, the primary sends a NOTIFY packet to the secondaries. This packet contains the SOA record for the zone and informs the secondary that it might benefit from a transfer. The secondary can choose not to transfer, if it sees a heavy load at that moment. The notification could be turned on, on a per zone basis, and might need a new bootfile parameter (NOTIFY/NONOTIFY) with the primary/secondary entry. This mechanism will be particularly useful in dynamic update situations where servers might need to converge to a common state, fast. The NOTIFY packet looks like this: Kumar, Hotz, Postel [Page 11] INTERNET DRAFT October, 1993 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| "NOTIFY" |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT = 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT = 1 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT = 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT = 0 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ / / / SOA RECORD / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ A mix of polling (current mechanism) and low-priority interrupts from the primary may be considered as the mechanism for zone transfers. 5.1 Unregistered Secondaries The primary server must be aware of the presence of all secondaries, including those that aren't registered ("registered" servers are those servers whose names are returned by DNS in response to an NS query for that zone), in order to send them NOTIFY messages. Note that this information is also needed if the primary is maintaining "soft state" (per the mechanism in section 1.1.2). Once again, there are two different means of doing this: a. Automatic Registration Any entity that initiates any kind of transfer (IXFR/AXFR) is identified as a potential candidate for notification and for the purpose of keeping soft state (as described in section 1.1.2). The IP address of this entity may be recorded against the zone it transferred. Old entries (more than some number of REFRESH periods old, say 10*REFRESH) may be timed out). b. "my-secondaries" We keep a list of "my-secondaries" servers that are not advertised via the DNS but are known servers for our domain (possibly serving local resolvers). Since the system administrator typically knows about unregistered secondaries, this merely formalizes their Kumar, Hotz, Postel [Page 12] INTERNET DRAFT October, 1993 existence. Such secondaries are usually local machines, mostly time-sharing machines on a campus or organization premises (within the domain) that serve as name servers for local queries, keeping the load on the registered secondaries, small. We propose an entry in the bootfile of each server that expects to see IXFR queries from other servers that reads: my-secondaries zone server1, server2, ....., serverN This is a list of secondaries that would normally transfer zone from this server. This entry may immediately follow the entry that says, primary zone datafile or secondary zone primary Thus, "my-secondaries" servers are associated with a given zone and could transfer from either a secondary or the primary server. Thus, a secondary that serves as source for zone data for other secondaries needs to maintain such a list, like the primary. In either case, please note that we do not address the issue of cached data at other servers. TTL values could be used to ensure that data is not cached for longer than it is likely to stay valid. 5.2 Timing and Security issues. Notification procedure necessitates that we ensure the following: a. There must be a minimum time between notifications. This prevents a malicious primary from bogging the secondary down, with back-to-back notifications. The secondary must maintain a timer (optionally) to enforce such restrictions. b. A secondary must transfer zone within a maximum interval (existing REFRESH mechanism should suffice). This ensures the state is not inconsistent for more than a fixed maximum interval. c. Modification Notification should be accepted only if coming from primary or the server you normally transfer from. A malicious network entity could pose as a primary and transfer incorrect data to you. This does not really solve the problem of impersonation since a masquerading entity could just as well act as the primary when sending the NOTIFY message. This just provides an additional hurdle so somebody actually sending you a NOTIFY does need to impersonate Kumar, Hotz, Postel [Page 13] INTERNET DRAFT October, 1993 the primary. d. When accepted, zone should be transferred only from primary or the server you normally transfer from. 6. Performance Issues DNS, like any other replicated, distributed system, has various parameters that can be tuned to get the desired nature of performance. For example, a shorter REFRESH cycle ensures a faster convergence among authoritative servers, at the cost of extra network bandwidth used in transferring zone data. Thus, system administrators tune this figure to the desired mix of consistency and bandwidth usage. Similarly, the TTL associated with each RR presents a trade-off between how often you query an authoritative server and how current your data is. A low TTL would keep the data current at the cost of extra network traffic while a high TTL conserves bandwidth but allows for the data to be inconsistent. System administrators balance between the two requirements and choose a reasonable TTL. Similar figures in the above scheme, such as the frequency of NOTIFY acceptance, the TTL of dynamic data and possibly the number of secondaries allowed, present trade-offs that need to be made to tune performance. A higher rate of NOTIFY acceptance will imply greater network traffic but very speedy convergence. A lower figure will conserve network bandwidth but will allow for data to be inconsistent for longer. A single server, serving a zone will imply instantaneous convergence but will provide very low availability and reliability. This might be alright for a low traffic volume zone. We do not, by way of the IXFR and NOTIFY mechanisms, hope to provide servers that converge instantaneously with minimal traffic. Studies in the future will show how effective these mechanisms will be and how best the various parameters can be used to tune the performance. 7. Acknowledgements We express our sincere thanks to Clifford Neuman, Masataka Ohta, Don Lewis, Philip Wood and Paul Vixie for their comments on earlier versions of this draft. Kumar, Hotz, Postel [Page 14] INTERNET DRAFT October, 1993 8. Authors' Addresses: Anant Kumar, Steve Hotz, Jon Postel @isi.edu USC Information Sciences Institute 4676 Admiralty Way Marina Del Rey CA 90292-6695 Phone:(310) 822-1511 FAX: (310) 823-6714 This Internet Draft expires April 12, 1994. Kumar, Hotz, Postel [Page 15] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~