Import upstream version 0.9.13
diff --git a/.tarball-version b/.tarball-version
new file mode 100644
index 0000000..62ea259
--- /dev/null
+++ b/.tarball-version
@@ -0,0 +1 @@
+0.9.13
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..daf60e4
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,7 @@
+Harald Welte <laforge@gnumonks.org>
+Holger Freyther <zecke@selfish.org>
+Jan Luebbe <jluebbe@debian.org>
+Stefan Schmidt <stefan@datenfreihafen.org>
+Daniel Willmann <daniel@totalueberwachung.de>
+Andreas Eversberg <Andreas.Eversberg@versatel.de>
+Sylvain Munaut <246tnt@gmail.com>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..dba13ed
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..c4d0aa1
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,13 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+SUBDIRS = include src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = openbsc.pc
+
+BUILT_SOURCES = $(top_srcdir)/.version
+$(top_srcdir)/.version:
+	echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+	echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..86bce3e
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,762 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = .
+DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
+	$(srcdir)/Makefile.in $(srcdir)/bscconfig.h.in \
+	$(srcdir)/openbsc.pc.in $(top_srcdir)/configure AUTHORS \
+	COPYING depcomp install-sh missing
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = bscconfig.h
+CONFIG_CLEAN_FILES = openbsc.pc
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+	html-recursive info-recursive install-data-recursive \
+	install-dvi-recursive install-exec-recursive \
+	install-html-recursive install-info-recursive \
+	install-pdf-recursive install-ps-recursive install-recursive \
+	installcheck-recursive installdirs-recursive pdf-recursive \
+	ps-recursive uninstall-recursive
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__installdirs = "$(DESTDIR)$(pkgconfigdir)"
+DATA = $(pkgconfig_DATA)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
+  distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+	$(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+	distdir dist dist-all distcheck
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+  { test ! -d "$(distdir)" \
+    || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+         && rm -fr "$(distdir)"; }; }
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+DIST_ARCHIVES = $(distdir).tar.gz $(distdir).tar.bz2
+GZIP_ENV = --best
+distuninstallcheck_listfiles = find . -type f -print
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+SUBDIRS = include src tests
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = openbsc.pc
+BUILT_SOURCES = $(top_srcdir)/.version
+all: $(BUILT_SOURCES) bscconfig.h
+	$(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+am--refresh:
+	@:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
+	      $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
+		&& exit 0; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    echo ' $(SHELL) ./config.status'; \
+	    $(SHELL) ./config.status;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	$(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	$(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	$(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+
+bscconfig.h: stamp-h1
+	@if test ! -f $@; then \
+	  rm -f stamp-h1; \
+	  $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \
+	else :; fi
+
+stamp-h1: $(srcdir)/bscconfig.h.in $(top_builddir)/config.status
+	@rm -f stamp-h1
+	cd $(top_builddir) && $(SHELL) ./config.status bscconfig.h
+$(srcdir)/bscconfig.h.in:  $(am__configure_deps) 
+	($(am__cd) $(top_srcdir) && $(AUTOHEADER))
+	rm -f stamp-h1
+	touch $@
+
+distclean-hdr:
+	-rm -f bscconfig.h stamp-h1
+openbsc.pc: $(top_builddir)/config.status $(srcdir)/openbsc.pc.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+install-pkgconfigDATA: $(pkgconfig_DATA)
+	@$(NORMAL_INSTALL)
+	test -z "$(pkgconfigdir)" || $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)"
+	@list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \
+	  $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \
+	done
+
+uninstall-pkgconfigDATA:
+	@$(NORMAL_UNINSTALL)
+	@list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	test -n "$$files" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(pkgconfigdir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(pkgconfigdir)" && rm -f $$files
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+#     (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	target=`echo $@ | sed s/-recursive//`; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    dot_seen=yes; \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done; \
+	if test "$$dot_seen" = "no"; then \
+	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+	fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	case "$@" in \
+	  distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+	  *) list='$(SUBDIRS)' ;; \
+	esac; \
+	rev=''; for subdir in $$list; do \
+	  if test "$$subdir" = "."; then :; else \
+	    rev="$$subdir $$rev"; \
+	  fi; \
+	done; \
+	rev="$$rev ."; \
+	target=`echo $@ | sed s/-recursive//`; \
+	for subdir in $$rev; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done && test -z "$$fail"
+tags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+	done
+ctags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+	done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES) bscconfig.h.in $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+	  include_option=--etags-include; \
+	  empty_fix=.; \
+	else \
+	  include_option=--include; \
+	  empty_fix=; \
+	fi; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test ! -f $$subdir/TAGS || \
+	      set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+	  fi; \
+	done; \
+	list='$(SOURCES) $(HEADERS) bscconfig.h.in $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES) bscconfig.h.in $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS) bscconfig.h.in $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	$(am__remove_distdir)
+	test -d "$(distdir)" || mkdir "$(distdir)"
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test -d "$(distdir)/$$subdir" \
+	    || $(MKDIR_P) "$(distdir)/$$subdir" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+	    $(am__relativize); \
+	    new_distdir=$$reldir; \
+	    dir1=$$subdir; dir2="$(top_distdir)"; \
+	    $(am__relativize); \
+	    new_top_distdir=$$reldir; \
+	    echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+	    echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+	    ($(am__cd) $$subdir && \
+	      $(MAKE) $(AM_MAKEFLAGS) \
+	        top_distdir="$$new_top_distdir" \
+	        distdir="$$new_distdir" \
+		am__remove_distdir=: \
+		am__skip_length_check=: \
+		am__skip_mode_fix=: \
+	        distdir) \
+	      || exit 1; \
+	  fi; \
+	done
+	$(MAKE) $(AM_MAKEFLAGS) \
+	  top_distdir="$(top_distdir)" distdir="$(distdir)" \
+	  dist-hook
+	-test -n "$(am__skip_mode_fix)" \
+	|| find "$(distdir)" -type d ! -perm -755 \
+		-exec chmod u+rwx,go+rx {} \; -o \
+	  ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+	  ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+	  ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+	|| chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+	tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+	$(am__remove_distdir)
+dist-bzip2: distdir
+	tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
+	$(am__remove_distdir)
+
+dist-lzma: distdir
+	tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma
+	$(am__remove_distdir)
+
+dist-xz: distdir
+	tardir=$(distdir) && $(am__tar) | xz -c >$(distdir).tar.xz
+	$(am__remove_distdir)
+
+dist-tarZ: distdir
+	tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+	$(am__remove_distdir)
+
+dist-shar: distdir
+	shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
+	$(am__remove_distdir)
+
+dist-zip: distdir
+	-rm -f $(distdir).zip
+	zip -rq $(distdir).zip $(distdir)
+	$(am__remove_distdir)
+
+dist dist-all: distdir
+	tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+	tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
+	$(am__remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration.  Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+	case '$(DIST_ARCHIVES)' in \
+	*.tar.gz*) \
+	  GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
+	*.tar.bz2*) \
+	  bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+	*.tar.lzma*) \
+	  lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\
+	*.tar.xz*) \
+	  xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+	*.tar.Z*) \
+	  uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+	*.shar.gz*) \
+	  GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
+	*.zip*) \
+	  unzip $(distdir).zip ;;\
+	esac
+	chmod -R a-w $(distdir); chmod a+w $(distdir)
+	mkdir $(distdir)/_build
+	mkdir $(distdir)/_inst
+	chmod a-w $(distdir)
+	test -d $(distdir)/_build || exit 0; \
+	dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+	  && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+	  && am__cwd=`pwd` \
+	  && $(am__cd) $(distdir)/_build \
+	  && ../configure --srcdir=.. --prefix="$$dc_install_base" \
+	    $(DISTCHECK_CONFIGURE_FLAGS) \
+	  && $(MAKE) $(AM_MAKEFLAGS) \
+	  && $(MAKE) $(AM_MAKEFLAGS) dvi \
+	  && $(MAKE) $(AM_MAKEFLAGS) check \
+	  && $(MAKE) $(AM_MAKEFLAGS) install \
+	  && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+	  && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+	  && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+	        distuninstallcheck \
+	  && chmod -R a-w "$$dc_install_base" \
+	  && ({ \
+	       (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+	            distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+	      } || { rm -rf "$$dc_destdir"; exit 1; }) \
+	  && rm -rf "$$dc_destdir" \
+	  && $(MAKE) $(AM_MAKEFLAGS) dist \
+	  && rm -rf $(DIST_ARCHIVES) \
+	  && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+	  && cd "$$am__cwd" \
+	  || exit 1
+	$(am__remove_distdir)
+	@(echo "$(distdir) archives ready for distribution: "; \
+	  list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+	  sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+	@$(am__cd) '$(distuninstallcheck_dir)' \
+	&& test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \
+	   || { echo "ERROR: files left after uninstall:" ; \
+	        if test -n "$(DESTDIR)"; then \
+	          echo "  (check DESTDIR support)"; \
+	        fi ; \
+	        $(distuninstallcheck_listfiles) ; \
+	        exit 1; } >&2
+distcleancheck: distclean
+	@if test '$(srcdir)' = . ; then \
+	  echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+	  exit 1 ; \
+	fi
+	@test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+	  || { echo "ERROR: files left in build directory after distclean:" ; \
+	       $(distcleancheck_listfiles) ; \
+	       exit 1; } >&2
+check-am: all-am
+check: $(BUILT_SOURCES)
+	$(MAKE) $(AM_MAKEFLAGS) check-recursive
+all-am: Makefile $(DATA) bscconfig.h
+installdirs: installdirs-recursive
+installdirs-am:
+	for dir in "$(DESTDIR)$(pkgconfigdir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: $(BUILT_SOURCES)
+	$(MAKE) $(AM_MAKEFLAGS) install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+	-test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-recursive
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-hdr distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-pkgconfigDATA
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
+	-rm -rf $(top_srcdir)/autom4te.cache
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-pkgconfigDATA
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all check \
+	ctags-recursive install install-am install-strip \
+	tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+	all all-am am--refresh check check-am clean clean-generic \
+	ctags ctags-recursive dist dist-all dist-bzip2 dist-gzip \
+	dist-hook dist-lzma dist-shar dist-tarZ dist-xz dist-zip \
+	distcheck distclean distclean-generic distclean-hdr \
+	distclean-tags distcleancheck distdir distuninstallcheck dvi \
+	dvi-am html html-am info info-am install install-am \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-pkgconfigDATA install-ps install-ps-am \
+	install-strip installcheck installcheck-am installdirs \
+	installdirs-am maintainer-clean maintainer-clean-generic \
+	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
+	tags-recursive uninstall uninstall-am uninstall-pkgconfigDATA
+
+$(top_srcdir)/.version:
+	echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+	echo $(VERSION) > $(distdir)/.tarball-version
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/README b/README
index e69de29..fa01ff4 100644
--- a/README
+++ b/README
@@ -0,0 +1,32 @@
+About OpenBSC
+=============
+
+OpenBSC is a minimalistic implementation of the GSM Network, with
+particular emphasis on the functionality typically provided by the BSC,
+MSC, HLR, VLR and SMSC.
+
+Its currently supported interfaces towards the BTS are:
+
+ * Classic A-bis over E1 using a mISDN based E1 interface. In other
+   words, you can connect existing GSM Base Transceiver Station (BTS)
+   through E1 to OpenBSC.  So far, we have only tested the Siemens BS-11
+   Test reports with other BTS are much appreciated!
+
+ * A-bis over IP as used by the ip.access nanoBTS product family
+
+You can find the project documentation at http://openbsc.gnumonks.org/
+
+This project is still in its early days, and there are lots of areas where it
+doesn't behave as per GSM spec.
+
+	Harald Welte <laforge@gnumonks.org>
+
+
+libosmocore
+===========
+
+Please note that as of March 2010, OpenBSC has a dependency to a library
+called "libosmocore".  You can obtain that library from
+
+	git://git.osmocom.org/libosmocore.git
+
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644
index 0000000..f239233
--- /dev/null
+++ b/aclocal.m4
@@ -0,0 +1,1148 @@
+# generated automatically by aclocal 1.11.1 -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2007, 2008, 2009  Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.67],,
+[m4_warning([this file was generated for autoconf 2.67.
+You have another version of autoconf.  It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically `autoreconf'.])])
+
+# pkg.m4 - Macros to locate and utilise pkg-config.            -*- Autoconf -*-
+# serial 1 (pkg-config-0.24)
+# 
+# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# PKG_PROG_PKG_CONFIG([MIN-VERSION])
+# ----------------------------------
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
+AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
+AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+	AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+	_pkg_min_version=m4_default([$1], [0.9.0])
+	AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+		AC_MSG_RESULT([yes])
+	else
+		AC_MSG_RESULT([no])
+		PKG_CONFIG=""
+	fi
+fi[]dnl
+])# PKG_PROG_PKG_CONFIG
+
+# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# Check to see whether a particular set of modules exists.  Similar
+# to PKG_CHECK_MODULES(), but does not set variables or print errors.
+#
+# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+# only at the first occurence in configure.ac, so if the first place
+# it's called might be skipped (such as if it is within an "if", you
+# have to call PKG_CHECK_EXISTS manually
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+  m4_default([$2], [:])
+m4_ifvaln([$3], [else
+  $3])dnl
+fi])
+
+# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+# ---------------------------------------------
+m4_define([_PKG_CONFIG],
+[if test -n "$$1"; then
+    pkg_cv_[]$1="$$1"
+ elif test -n "$PKG_CONFIG"; then
+    PKG_CHECK_EXISTS([$3],
+                     [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
+		     [pkg_failed=yes])
+ else
+    pkg_failed=untried
+fi[]dnl
+])# _PKG_CONFIG
+
+# _PKG_SHORT_ERRORS_SUPPORTED
+# -----------------------------
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi[]dnl
+])# _PKG_SHORT_ERRORS_SUPPORTED
+
+
+# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+# [ACTION-IF-NOT-FOUND])
+#
+#
+# Note that if there is a possibility the first call to
+# PKG_CHECK_MODULES might not happen, you should be sure to include an
+# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+#
+#
+# --------------------------------------------------------------
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $1])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+   	AC_MSG_RESULT([no])
+        _PKG_SHORT_ERRORS_SUPPORTED
+        if test $_pkg_short_errors_supported = yes; then
+	        $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1`
+        else 
+	        $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+	m4_default([$4], [AC_MSG_ERROR(
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT])[]dnl
+        ])
+elif test $pkg_failed = untried; then
+     	AC_MSG_RESULT([no])
+	m4_default([$4], [AC_MSG_FAILURE(
+[The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl
+        ])
+else
+	$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+	$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+        AC_MSG_RESULT([yes])
+	$3
+fi[]dnl
+])# PKG_CHECK_MODULES
+
+# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.11'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version.  Point them to the right macro.
+m4_if([$1], [1.11.1], [],
+      [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too.  Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.11.1])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
+
+# Copyright (C) 2001, 2003, 2005  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to `$srcdir/foo'.  In other projects, it is set to
+# `$srcdir', `$srcdir/..', or `$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory.  The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run.  This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+#    fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+#    fails if $ac_aux_dir is absolute,
+#    fails when called from a subdirectory in a VPATH build with
+#          a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir.  In an in-source build this is usually
+# harmless because $srcdir is `.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir.  That would be:
+#   am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+#   MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH.  The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[dnl Rely on autoconf to set up CDPATH properly.
+AC_PREREQ([2.50])dnl
+# expand $ac_aux_dir to an absolute path
+am_aux_dir=`cd $ac_aux_dir && pwd`
+])
+
+# AM_CONDITIONAL                                            -*- Autoconf -*-
+
+# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 9
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ(2.52)dnl
+ ifelse([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
+	[$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+  $1_TRUE=
+  $1_FALSE='#'
+else
+  $1_TRUE='#'
+  $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+  AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 10
+
+# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery.  Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "GCJ", or "OBJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+ifelse([$1], CC,   [depcc="$CC"   am_compiler_list=],
+       [$1], CXX,  [depcc="$CXX"  am_compiler_list=],
+       [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+       [$1], UPC,  [depcc="$UPC"  am_compiler_list=],
+       [$1], GCJ,  [depcc="$GCJ"  am_compiler_list='gcc3 gcc'],
+                   [depcc="$$1"   am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+               [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named `D' -- because `-MD' means `put the output
+  # in D'.
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_$1_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+  fi
+  am__universal=false
+  m4_case([$1], [CC],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac],
+    [CXX],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac])
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with
+      # Solaris 8's {/usr,}/bin/sh.
+      touch sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with `-c' and `-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle `-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # after this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested
+      if test "x$enable_dependency_tracking" = xyes; then
+	continue
+      else
+	break
+      fi
+      ;;
+    msvisualcpp | msvcmsys)
+      # This compiler won't grok `-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_$1_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE(dependency-tracking,
+[  --disable-dependency-tracking  speeds up one-time build
+  --enable-dependency-tracking   do not reject slow dependency extractors])
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+])
+
+# Generate code to set up dependency tracking.              -*- Autoconf -*-
+
+# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+#serial 5
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+  # Autoconf 2.62 quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named `Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`AS_DIRNAME("$mf")`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running `make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # When using ansi2knr, U may be empty or an underscore; expand it
+    U=`sed -n 's/^U = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+	 sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`AS_DIRNAME(["$file"])`
+      AS_MKDIR_P([$dirpart/$fdir])
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking
+# is enabled.  FIXME.  This creates each `.P' file that we will
+# need in order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+     [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+     [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"])
+])
+
+# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 8
+
+# AM_CONFIG_HEADER is obsolete.  It has been replaced by AC_CONFIG_HEADERS.
+AU_DEFUN([AM_CONFIG_HEADER], [AC_CONFIG_HEADERS($@)])
+
+# Do all the work for Automake.                             -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2008, 2009 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 16
+
+# This macro actually does too much.  Some checks are only needed if
+# your package does certain things.  But this isn't really a big deal.
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out.  PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition.  After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.62])dnl
+dnl Autoconf wants to disallow AM_ names.  We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,,
+  [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package])
+ AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version})
+AM_MISSING_PROG(AUTOCONF, autoconf)
+AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version})
+AM_MISSING_PROG(AUTOHEADER, autoheader)
+AM_MISSING_PROG(MAKEINFO, makeinfo)
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AM_PROG_MKDIR_P])dnl
+# We need awk for the "check" target.  The system "awk" is bad on
+# some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+	      [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+			     [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+		  [_AM_DEPENDENCIES(CC)],
+		  [define([AC_PROG_CC],
+			  defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+		  [_AM_DEPENDENCIES(CXX)],
+		  [define([AC_PROG_CXX],
+			  defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+		  [_AM_DEPENDENCIES(OBJC)],
+		  [define([AC_PROG_OBJC],
+			  defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl
+])
+_AM_IF_OPTION([silent-rules], [AC_REQUIRE([AM_SILENT_RULES])])dnl
+dnl The `parallel-tests' driver may need to know about EXEEXT, so add the
+dnl `am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen.  This macro
+dnl is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+  [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+])
+
+dnl Hook into `_AC_COMPILER_EXEEXT' early to learn its expansion.  Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated.  The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001, 2003, 2005, 2008  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+AC_SUBST(install_sh)])
+
+# Copyright (C) 2003, 2005  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 2
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot.  For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Check to see how 'make' treats includes.	            -*- Autoconf -*-
+
+# Copyright (C) 2001, 2002, 2003, 2005, 2009  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 4
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check to see how make treats includes.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+	@echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+AC_MSG_CHECKING([for style of include used by $am_make])
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from `make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+AC_SUBST([am__include])
+AC_SUBST([am__quote])
+AC_MSG_RESULT([$_am_result])
+rm -f confinc confmf
+])
+
+# Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
+
+# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 6
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it supports --run.
+# If it does, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --run true"; then
+  am_missing_run="$MISSING --run "
+else
+  am_missing_run=
+  AC_MSG_WARN([`missing' script is too old or missing])
+fi
+])
+
+# Copyright (C) 2003, 2004, 2005, 2006  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_MKDIR_P
+# ---------------
+# Check for `mkdir -p'.
+AC_DEFUN([AM_PROG_MKDIR_P],
+[AC_PREREQ([2.60])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+dnl Automake 1.8 to 1.9.6 used to define mkdir_p.  We now use MKDIR_P,
+dnl while keeping a definition of mkdir_p for backward compatibility.
+dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile.
+dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of
+dnl Makefile.ins that do not define MKDIR_P, so we do our own
+dnl adjustment using top_builddir (which is defined more often than
+dnl MKDIR_P).
+AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl
+case $mkdir_p in
+  [[\\/$]]* | ?:[[\\/]]*) ;;
+  */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
+esac
+])
+
+# Helper functions for option handling.                     -*- Autoconf -*-
+
+# Copyright (C) 2001, 2002, 2003, 2005, 2008  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 4
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# ------------------------------
+# Set option NAME.  Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), 1)])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ----------------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Check to make sure that the build environment is sane.    -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 5
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Just in case
+sleep 1
+echo timestamp > conftest.file
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[[\\\"\#\$\&\'\`$am_lf]]*)
+    AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+  *[[\\\"\#\$\&\'\`$am_lf\ \	]]*)
+    AC_MSG_ERROR([unsafe srcdir value: `$srcdir']);;
+esac
+
+# Do `set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+   if test "$[*]" = "X"; then
+      # -L didn't work.
+      set X `ls -t "$srcdir/configure" conftest.file`
+   fi
+   rm -f conftest.file
+   if test "$[*]" != "X $srcdir/configure conftest.file" \
+      && test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+      # If neither matched, then we have a broken ls.  This can happen
+      # if, for instance, CONFIG_SHELL is bash and it inherits a
+      # broken ls alias from the environment.  This has actually
+      # happened.  Such a system could not be considered "sane".
+      AC_MSG_ERROR([ls -t appears to fail.  Make sure there is not a broken
+alias in your environment])
+   fi
+
+   test "$[2]" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT(yes)])
+
+# Copyright (C) 2009  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 1
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# (`yes' being less verbose, `no' or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules],
+[  --enable-silent-rules          less verbose build output (undo: `make V=1')
+  --disable-silent-rules         verbose build output (undo: `make V=0')])
+case $enable_silent_rules in
+yes) AM_DEFAULT_VERBOSITY=0;;
+no)  AM_DEFAULT_VERBOSITY=1;;
+*)   AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001, 2003, 2005  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor `install' (even GNU) is that you can't
+# specify the program used to strip binaries.  This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in `make install-strip', and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using `strip' when the user
+# run `make install-strip'.  However `strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the `STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be `maybe'.
+if test "$cross_compiling" != no; then
+  AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006, 2008  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 2
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball.                            -*- Autoconf -*-
+
+# Copyright (C) 2004, 2005  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 2
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of `v7', `ustar', or `pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+#     tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+#     $(am__untar) < result.tar
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility.
+AM_MISSING_PROG([AMTAR], [tar])
+m4_if([$1], [v7],
+     [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'],
+     [m4_case([$1], [ustar],, [pax],,
+              [m4_fatal([Unknown tar format])])
+AC_MSG_CHECKING([how to create a $1 tar archive])
+# Loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+_am_tools=${am_cv_prog_tar_$1-$_am_tools}
+# Do not fold the above two line into one, because Tru64 sh and
+# Solaris sh will not grok spaces in the rhs of `-'.
+for _am_tool in $_am_tools
+do
+  case $_am_tool in
+  gnutar)
+    for _am_tar in tar gnutar gtar;
+    do
+      AM_RUN_LOG([$_am_tar --version]) && break
+    done
+    am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
+    am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
+    am__untar="$_am_tar -xf -"
+    ;;
+  plaintar)
+    # Must skip GNU tar: if it does not support --format= it doesn't create
+    # ustar tarball either.
+    (tar --version) >/dev/null 2>&1 && continue
+    am__tar='tar chf - "$$tardir"'
+    am__tar_='tar chf - "$tardir"'
+    am__untar='tar xf -'
+    ;;
+  pax)
+    am__tar='pax -L -x $1 -w "$$tardir"'
+    am__tar_='pax -L -x $1 -w "$tardir"'
+    am__untar='pax -r'
+    ;;
+  cpio)
+    am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+    am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+    am__untar='cpio -i -H $1 -d'
+    ;;
+  none)
+    am__tar=false
+    am__tar_=false
+    am__untar=false
+    ;;
+  esac
+
+  # If the value was cached, stop now.  We just wanted to have am__tar
+  # and am__untar set.
+  test -n "${am_cv_prog_tar_$1}" && break
+
+  # tar/untar a dummy directory, and stop if the command works
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  echo GrepMe > conftest.dir/file
+  AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+  rm -rf conftest.dir
+  if test -s conftest.tar; then
+    AM_RUN_LOG([$am__untar <conftest.tar])
+    grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+  fi
+done
+rm -rf conftest.dir
+
+AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
diff --git a/bscconfig.h.in b/bscconfig.h.in
new file mode 100644
index 0000000..fc5dfde
--- /dev/null
+++ b/bscconfig.h.in
@@ -0,0 +1,61 @@
+/* bscconfig.h.in.  Generated from configure.in by autoheader.  */
+
+/* Define to 1 if you have the <dahdi/user.h> header file. */
+#undef HAVE_DAHDI_USER_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
+
+/* Use crypt functionality of vty. */
+#undef VTY_CRYPT_PW
diff --git a/configure b/configure
new file mode 100755
index 0000000..86e47ae
--- /dev/null
+++ b/configure
@@ -0,0 +1,6307 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.67 for openbsc 0.9.13.
+#
+# Report bugs to <openbsc-devel@lists.openbsc.org>.
+#
+#
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
+# Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+	 /*)
+	   for as_base in sh bash ksh sh5; do
+	     # Try only shells that exist, to save several forks.
+	     as_shell=$as_dir/$as_base
+	     if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+		    { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+		   if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+	   done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+	      { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  # We cannot yet assume a decent shell, so we have to provide a
+	# neutralization value for shells without unset; and this also
+	# works around shells that cannot unset nonexistent variables.
+	BASH_ENV=/dev/null
+	ENV=/dev/null
+	(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+	export CONFIG_SHELL
+	exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"}
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell bug-autoconf@gnu.org and
+$0: openbsc-devel@lists.openbsc.org about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -p'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -p'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -p'
+  fi
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+	test -d "$1/.";
+      else
+	case $1 in #(
+	-*)set "./$1";;
+	esac;
+	case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
+	???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='openbsc'
+PACKAGE_TARNAME='openbsc'
+PACKAGE_VERSION='0.9.13'
+PACKAGE_STRING='openbsc 0.9.13'
+PACKAGE_BUGREPORT='openbsc-devel@lists.openbsc.org'
+PACKAGE_URL=''
+
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='am__EXEEXT_FALSE
+am__EXEEXT_TRUE
+LTLIBOBJS
+LIBOBJS
+COVERAGE_LDFLAGS
+COVERAGE_CFLAGS
+SYMBOL_VISIBILITY
+EGREP
+GREP
+CPP
+LIBOSMOVTY_LIBS
+LIBOSMOVTY_CFLAGS
+LIBOSMOCORE_LIBS
+LIBOSMOCORE_CFLAGS
+BUILD_BSC_FALSE
+BUILD_BSC_TRUE
+BUILD_NAT_FALSE
+BUILD_NAT_TRUE
+LIBOSMOSCCP_LIBS
+LIBOSMOSCCP_CFLAGS
+PKG_CONFIG_LIBDIR
+PKG_CONFIG_PATH
+PKG_CONFIG
+HAVE_LIBGTP_FALSE
+HAVE_LIBGTP_TRUE
+GPRS_LIBGTP
+RANLIB
+am__fastdepCC_FALSE
+am__fastdepCC_TRUE
+CCDEPMODE
+AMDEPBACKSLASH
+AMDEP_FALSE
+AMDEP_TRUE
+am__quote
+am__include
+DEPDIR
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+AM_BACKSLASH
+AM_DEFAULT_VERBOSITY
+am__untar
+am__tar
+AMTAR
+am__leading_dot
+SET_MAKE
+AWK
+mkdir_p
+MKDIR_P
+INSTALL_STRIP_PROGRAM
+STRIP
+install_sh
+MAKEINFO
+AUTOHEADER
+AUTOMAKE
+AUTOCONF
+ACLOCAL
+VERSION
+PACKAGE
+CYGPATH_W
+am__isrc
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_silent_rules
+enable_dependency_tracking
+enable_nat
+enable_osmo_bsc
+enable_coverage
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+PKG_CONFIG
+PKG_CONFIG_PATH
+PKG_CONFIG_LIBDIR
+LIBOSMOSCCP_CFLAGS
+LIBOSMOSCCP_LIBS
+LIBOSMOCORE_CFLAGS
+LIBOSMOCORE_LIBS
+LIBOSMOVTY_CFLAGS
+LIBOSMOVTY_LIBS
+CPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
+		datadir sysconfdir sharedstatedir localstatedir includedir \
+		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+		libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+    $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host.
+    If a cross compiler is detected then cross compile mode will be used" >&2
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_myself" : 'X\(//\)[^/]' \| \
+	 X"$as_myself" : 'X\(//\)$' \| \
+	 X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+	cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+	pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures openbsc 0.9.13 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/openbsc]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+
+Program names:
+  --program-prefix=PREFIX            prepend PREFIX to installed program names
+  --program-suffix=SUFFIX            append SUFFIX to installed program names
+  --program-transform-name=PROGRAM   run sed PROGRAM on installed program names
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of openbsc 0.9.13:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Features:
+  --disable-option-checking  ignore unrecognized --enable/--with options
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --enable-silent-rules          less verbose build output (undo: `make V=1')
+  --disable-silent-rules         verbose build output (undo: `make V=0')
+  --disable-dependency-tracking  speeds up one-time build
+  --enable-dependency-tracking   do not reject slow dependency extractors
+  --enable-nat            Build the BSC NAT. Requires SCCP
+  --enable-osmo-bsc       Build the Osmo BSC
+  --enable-coverage       enable code coverage support [default=no]
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  PKG_CONFIG  path to pkg-config utility
+  PKG_CONFIG_PATH
+              directories to add to pkg-config's search path
+  PKG_CONFIG_LIBDIR
+              path overriding pkg-config's built-in search path
+  LIBOSMOSCCP_CFLAGS
+              C compiler flags for LIBOSMOSCCP, overriding pkg-config
+  LIBOSMOSCCP_LIBS
+              linker flags for LIBOSMOSCCP, overriding pkg-config
+  LIBOSMOCORE_CFLAGS
+              C compiler flags for LIBOSMOCORE, overriding pkg-config
+  LIBOSMOCORE_LIBS
+              linker flags for LIBOSMOCORE, overriding pkg-config
+  LIBOSMOVTY_CFLAGS
+              C compiler flags for LIBOSMOVTY, overriding pkg-config
+  LIBOSMOVTY_LIBS
+              linker flags for LIBOSMOVTY, overriding pkg-config
+  CPP         C preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <openbsc-devel@lists.openbsc.org>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+openbsc configure 0.9.13
+generated by GNU Autoconf 2.67
+
+Copyright (C) 2010 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+	 test "$cross_compiling" = yes ||
+	 $as_test_x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if eval "test \"\${$3+set}\"" = set; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval "test \"\${$3+set}\"" = set; then :
+  $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+  # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_header_compiler=yes
+else
+  ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  ac_header_preproc=yes
+else
+  ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So?  What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+  yes:no: )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+  no:yes:* )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2:     check for missing prerequisite headers?" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+( $as_echo "## ---------------------------------------------- ##
+## Report this to openbsc-devel@lists.openbsc.org ##
+## ---------------------------------------------- ##"
+     ) | sed "s/^/$as_me: WARNING:     /" >&2
+    ;;
+esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval "test \"\${$3+set}\"" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+
+} # ac_fn_c_check_header_mongrel
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval "test \"\${$3+set}\"" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
+
+} # ac_fn_c_check_header_compile
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by openbsc $as_me 0.9.13, which was
+generated by GNU Autoconf 2.67.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+	ac_must_keep_next=false # Got value, back to normal.
+      else
+	case $ac_arg in
+	  *=* | --config-cache | -C | -disable-* | --disable-* \
+	  | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+	  | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+	  | -with-* | --with-* | -without-* | --without-* | --x)
+	    case "$ac_configure_args0 " in
+	      "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+	    esac
+	    ;;
+	  -* ) ac_must_keep_next=true ;;
+	esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+	"s/'\''/'\''\\\\'\'''\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+	eval ac_val=\$$ac_var
+	case $ac_val in
+	*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+	esac
+	$as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5 ; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+	# differences in whitespace do not lead to failure.
+	ac_old_val_w=`echo x $ac_old_val`
+	ac_new_val_w=`echo x $ac_new_val`
+	if test "$ac_old_val_w" != "$ac_new_val_w"; then
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+	  ac_cache_corrupted=:
+	else
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+	  eval $ac_var=\$ac_old_val
+	fi
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+am__api_version='1.11'
+
+ac_aux_dir=
+for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if test "${ac_cv_path_install+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+  ./ | .// | /[cC]/* | \
+  /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+  ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+  /usr/ucb/* ) ;;
+  *)
+    # OSF1 and SCO ODT 3.0 have their own names for install.
+    # Don't use installbsd from OSF since it installs stuff as root
+    # by default.
+    for ac_prog in ginstall scoinst install; do
+      for ac_exec_ext in '' $ac_executable_extensions; do
+	if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then
+	  if test $ac_prog = install &&
+	    grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # AIX install.  It has an incompatible calling convention.
+	    :
+	  elif test $ac_prog = install &&
+	    grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # program-specific install script used by HP pwplus--don't use.
+	    :
+	  else
+	    rm -rf conftest.one conftest.two conftest.dir
+	    echo one > conftest.one
+	    echo two > conftest.two
+	    mkdir conftest.dir
+	    if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+	      test -s conftest.one && test -s conftest.two &&
+	      test -s conftest.dir/conftest.one &&
+	      test -s conftest.dir/conftest.two
+	    then
+	      ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+	      break 3
+	    fi
+	  fi
+	fi
+      done
+    done
+    ;;
+esac
+
+  done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL=$ac_cv_path_install
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    INSTALL=$ac_install_sh
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5
+$as_echo_n "checking whether build environment is sane... " >&6; }
+# Just in case
+sleep 1
+echo timestamp > conftest.file
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[\\\"\#\$\&\'\`$am_lf]*)
+    as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5 ;;
+esac
+case $srcdir in
+  *[\\\"\#\$\&\'\`$am_lf\ \	]*)
+    as_fn_error $? "unsafe srcdir value: \`$srcdir'" "$LINENO" 5 ;;
+esac
+
+# Do `set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+   if test "$*" = "X"; then
+      # -L didn't work.
+      set X `ls -t "$srcdir/configure" conftest.file`
+   fi
+   rm -f conftest.file
+   if test "$*" != "X $srcdir/configure conftest.file" \
+      && test "$*" != "X conftest.file $srcdir/configure"; then
+
+      # If neither matched, then we have a broken ls.  This can happen
+      # if, for instance, CONFIG_SHELL is bash and it inherits a
+      # broken ls alias from the environment.  This has actually
+      # happened.  Such a system could not be considered "sane".
+      as_fn_error $? "ls -t appears to fail.  Make sure there is not a broken
+alias in your environment" "$LINENO" 5
+   fi
+
+   test "$2" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   as_fn_error $? "newly created file is older than distributed files!
+Check your system clock" "$LINENO" 5
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+test "$program_prefix" != NONE &&
+  program_transform_name="s&^&$program_prefix&;$program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+  program_transform_name="s&\$&$program_suffix&;$program_transform_name"
+# Double any \ or $.
+# By default was `s,x,x', remove it if useless.
+ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
+program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
+
+# expand $ac_aux_dir to an absolute path
+am_aux_dir=`cd $ac_aux_dir && pwd`
+
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --run true"; then
+  am_missing_run="$MISSING --run "
+else
+  am_missing_run=
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`missing' script is too old or missing" >&5
+$as_echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;}
+fi
+
+if test x"${install_sh}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+
+# Installed binaries are usually stripped using `strip' when the user
+# run `make install-strip'.  However `strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the `STRIP' environment variable to overrule this program.
+if test "$cross_compiling" != no; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_STRIP+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_ac_ct_STRIP+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5
+$as_echo_n "checking for a thread-safe mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+  if test "${ac_cv_path_mkdir+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in mkdir gmkdir; do
+	 for ac_exec_ext in '' $ac_executable_extensions; do
+	   { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; } || continue
+	   case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
+	     'mkdir (GNU coreutils) '* | \
+	     'mkdir (coreutils) '* | \
+	     'mkdir (fileutils) '4.1*)
+	       ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
+	       break 3;;
+	   esac
+	 done
+       done
+  done
+IFS=$as_save_IFS
+
+fi
+
+  test -d ./--version && rmdir ./--version
+  if test "${ac_cv_path_mkdir+set}" = set; then
+    MKDIR_P="$ac_cv_path_mkdir -p"
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for MKDIR_P within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    MKDIR_P="$ac_install_sh -d"
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+$as_echo "$MKDIR_P" >&6; }
+
+mkdir_p="$MKDIR_P"
+case $mkdir_p in
+  [\\/$]* | ?:[\\/]*) ;;
+  */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
+esac
+
+for ac_prog in gawk mawk nawk awk
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_AWK+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AWK"; then
+  ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_AWK="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$AWK" && break
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+	@echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  am__isrc=' -I$(srcdir)'
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+
+
+# Define the identity of the package.
+ PACKAGE='openbsc'
+ VERSION='0.9.13'
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE "$PACKAGE"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define VERSION "$VERSION"
+_ACEOF
+
+# Some tools Automake needs.
+
+ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"}
+
+
+AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"}
+
+
+AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"}
+
+
+AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
+
+
+MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
+
+# We need awk for the "check" target.  The system "awk" is bad on
+# some platforms.
+# Always define AMTAR for backward compatibility.
+
+AMTAR=${AMTAR-"${am_missing_run}tar"}
+
+am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'
+
+
+
+
+
+
+# Check whether --enable-silent-rules was given.
+if test "${enable_silent_rules+set}" = set; then :
+  enableval=$enable_silent_rules;
+fi
+
+case $enable_silent_rules in
+yes) AM_DEFAULT_VERBOSITY=0;;
+no)  AM_DEFAULT_VERBOSITY=1;;
+*)   AM_DEFAULT_VERBOSITY=0;;
+esac
+AM_BACKSLASH='\'
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+	@echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_CC+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5 ; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+	;;
+    [ab].out )
+	# We found the default executable, but exeext='' is most
+	# certainly right.
+	break;;
+    *.* )
+	if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+	then :; else
+	   ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	fi
+	# We set ac_cv_exeext here because the later test for it is not
+	# safe: cross compilers may not add the suffix if given an `-o'
+	# argument, so we may need to know it at that point already.
+	# Even if this section looks crufty: it has the advantage of
+	# actually working.
+	break;;
+    * )
+	break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5 ; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	  break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5 ; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+	cross_compiling=yes
+    else
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5 ; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if test "${ac_cv_objext+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5 ; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if test "${ac_cv_c_compiler_gnu+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if test "${ac_cv_prog_cc_g+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+	 CFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if test "${ac_cv_prog_cc_c89+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+	-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+DEPDIR="${am__leading_dot}deps"
+
+ac_config_commands="$ac_config_commands depfiles"
+
+
+am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+	@echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
+$as_echo_n "checking for style of include used by $am_make... " >&6; }
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from `make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
+$as_echo "$_am_result" >&6; }
+rm -f confinc confmf
+
+# Check whether --enable-dependency-tracking was given.
+if test "${enable_dependency_tracking+set}" = set; then :
+  enableval=$enable_dependency_tracking;
+fi
+
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+fi
+ if test "x$enable_dependency_tracking" != xno; then
+  AMDEP_TRUE=
+  AMDEP_FALSE='#'
+else
+  AMDEP_TRUE='#'
+  AMDEP_FALSE=
+fi
+
+
+
+depcc="$CC"   am_compiler_list=
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
+$as_echo_n "checking dependency style of $depcc... " >&6; }
+if test "${am_cv_CC_dependencies_compiler_type+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named `D' -- because `-MD' means `put the output
+  # in D'.
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_CC_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
+  fi
+  am__universal=false
+  case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with
+      # Solaris 8's {/usr,}/bin/sh.
+      touch sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with `-c' and `-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle `-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # after this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested
+      if test "x$enable_dependency_tracking" = xyes; then
+	continue
+      else
+	break
+      fi
+      ;;
+    msvisualcpp | msvcmsys)
+      # This compiler won't grok `-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_CC_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_CC_dependencies_compiler_type=none
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5
+$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; }
+CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
+
+ if
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
+  am__fastdepCC_TRUE=
+  am__fastdepCC_FALSE='#'
+else
+  am__fastdepCC_TRUE='#'
+  am__fastdepCC_FALSE=
+fi
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_RANLIB+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+  ac_ct_RANLIB=$RANLIB
+  # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_prog_ac_ct_RANLIB+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_RANLIB"; then
+  ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_RANLIB="ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_RANLIB" = x; then
+    RANLIB=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    RANLIB=$ac_ct_RANLIB
+  fi
+else
+  RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing crypt" >&5
+$as_echo_n "checking for library containing crypt... " >&6; }
+if test "${ac_cv_search_crypt+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char crypt ();
+int
+main ()
+{
+return crypt ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' crypt; do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_search_crypt=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext
+  if test "${ac_cv_search_crypt+set}" = set; then :
+  break
+fi
+done
+if test "${ac_cv_search_crypt+set}" = set; then :
+
+else
+  ac_cv_search_crypt=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_crypt" >&5
+$as_echo "$ac_cv_search_crypt" >&6; }
+ac_res=$ac_cv_search_crypt
+if test "$ac_res" != no; then :
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+  LIBCRYPT="-lcrypt";
+$as_echo "#define VTY_CRYPT_PW /**/" >>confdefs.h
+
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gtp_new" >&5
+$as_echo_n "checking for library containing gtp_new... " >&6; }
+if test "${ac_cv_search_gtp_new+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gtp_new ();
+int
+main ()
+{
+return gtp_new ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' gtp; do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_search_gtp_new=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext
+  if test "${ac_cv_search_gtp_new+set}" = set; then :
+  break
+fi
+done
+if test "${ac_cv_search_gtp_new+set}" = set; then :
+
+else
+  ac_cv_search_gtp_new=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gtp_new" >&5
+$as_echo "$ac_cv_search_gtp_new" >&6; }
+ac_res=$ac_cv_search_gtp_new
+if test "$ac_res" != no; then :
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+  LIBCRYPT="-lgtp"; GPRS_LIBGTP=1
+
+fi
+
+
+ if test "x$GPRS_LIBGTP" != "x"; then
+  HAVE_LIBGTP_TRUE=
+  HAVE_LIBGTP_FALSE='#'
+else
+  HAVE_LIBGTP_TRUE='#'
+  HAVE_LIBGTP_FALSE=
+fi
+
+
+
+
+
+
+
+
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+	if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
+set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_path_PKG_CONFIG+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_PKG_CONFIG"; then
+  ac_pt_PKG_CONFIG=$PKG_CONFIG
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if test "${ac_cv_path_ac_pt_PKG_CONFIG+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $ac_pt_PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
+if test -n "$ac_pt_PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_pt_PKG_CONFIG" = x; then
+    PKG_CONFIG=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    PKG_CONFIG=$ac_pt_PKG_CONFIG
+  fi
+else
+  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+fi
+
+fi
+if test -n "$PKG_CONFIG"; then
+	_pkg_min_version=0.9.0
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	else
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+		PKG_CONFIG=""
+	fi
+fi
+# Check whether --enable-nat was given.
+if test "${enable_nat+set}" = set; then :
+  enableval=$enable_nat;
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOSCCP" >&5
+$as_echo_n "checking for LIBOSMOSCCP... " >&6; }
+
+if test -n "$LIBOSMOSCCP_CFLAGS"; then
+    pkg_cv_LIBOSMOSCCP_CFLAGS="$LIBOSMOSCCP_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOSCCP_CFLAGS=`$PKG_CONFIG --cflags "libosmo-sccp >= 0.0.2" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$LIBOSMOSCCP_LIBS"; then
+    pkg_cv_LIBOSMOSCCP_LIBS="$LIBOSMOSCCP_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOSCCP_LIBS=`$PKG_CONFIG --libs "libosmo-sccp >= 0.0.2" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmo-sccp >= 0.0.2" 2>&1`
+        else
+	        LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmo-sccp >= 0.0.2" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBOSMOSCCP_PKG_ERRORS" >&5
+
+	as_fn_error $? "Package requirements (libosmo-sccp >= 0.0.2) were not met:
+
+$LIBOSMOSCCP_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS
+and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details." "$LINENO" 5
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS
+and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details" "$LINENO" 5 ; }
+else
+	LIBOSMOSCCP_CFLAGS=$pkg_cv_LIBOSMOSCCP_CFLAGS
+	LIBOSMOSCCP_LIBS=$pkg_cv_LIBOSMOSCCP_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+fi
+        osmo_ac_build_nat="yes"
+
+else
+
+        osmo_ac_build_nat="no"
+
+fi
+
+ if test "x$osmo_ac_build_nat" = "xyes"; then
+  BUILD_NAT_TRUE=
+  BUILD_NAT_FALSE='#'
+else
+  BUILD_NAT_TRUE='#'
+  BUILD_NAT_FALSE=
+fi
+
+
+# Check whether --enable-osmo-bsc was given.
+if test "${enable_osmo_bsc+set}" = set; then :
+  enableval=$enable_osmo_bsc;
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOSCCP" >&5
+$as_echo_n "checking for LIBOSMOSCCP... " >&6; }
+
+if test -n "$LIBOSMOSCCP_CFLAGS"; then
+    pkg_cv_LIBOSMOSCCP_CFLAGS="$LIBOSMOSCCP_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOSCCP_CFLAGS=`$PKG_CONFIG --cflags "libosmo-sccp >= 0.0.2" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$LIBOSMOSCCP_LIBS"; then
+    pkg_cv_LIBOSMOSCCP_LIBS="$LIBOSMOSCCP_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmo-sccp >= 0.0.2\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmo-sccp >= 0.0.2") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOSCCP_LIBS=`$PKG_CONFIG --libs "libosmo-sccp >= 0.0.2" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmo-sccp >= 0.0.2" 2>&1`
+        else
+	        LIBOSMOSCCP_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmo-sccp >= 0.0.2" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBOSMOSCCP_PKG_ERRORS" >&5
+
+	as_fn_error $? "Package requirements (libosmo-sccp >= 0.0.2) were not met:
+
+$LIBOSMOSCCP_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS
+and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details." "$LINENO" 5
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBOSMOSCCP_CFLAGS
+and LIBOSMOSCCP_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details" "$LINENO" 5 ; }
+else
+	LIBOSMOSCCP_CFLAGS=$pkg_cv_LIBOSMOSCCP_CFLAGS
+	LIBOSMOSCCP_LIBS=$pkg_cv_LIBOSMOSCCP_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+fi
+        osmo_ac_build_bsc="yes"
+
+else
+
+        osmo_ac_build_bsc="no"
+
+fi
+
+ if test "x$osmo_ac_build_bsc" = "xyes"; then
+  BUILD_BSC_TRUE=
+  BUILD_BSC_FALSE='#'
+else
+  BUILD_BSC_TRUE='#'
+  BUILD_BSC_FALSE=
+fi
+
+
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOCORE" >&5
+$as_echo_n "checking for LIBOSMOCORE... " >&6; }
+
+if test -n "$LIBOSMOCORE_CFLAGS"; then
+    pkg_cv_LIBOSMOCORE_CFLAGS="$LIBOSMOCORE_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmocore >= 0.1.30\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmocore >= 0.1.30") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOCORE_CFLAGS=`$PKG_CONFIG --cflags "libosmocore >= 0.1.30" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$LIBOSMOCORE_LIBS"; then
+    pkg_cv_LIBOSMOCORE_LIBS="$LIBOSMOCORE_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmocore >= 0.1.30\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmocore >= 0.1.30") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOCORE_LIBS=`$PKG_CONFIG --libs "libosmocore >= 0.1.30" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBOSMOCORE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmocore >= 0.1.30" 2>&1`
+        else
+	        LIBOSMOCORE_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmocore >= 0.1.30" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBOSMOCORE_PKG_ERRORS" >&5
+
+	as_fn_error $? "Package requirements (libosmocore >= 0.1.30) were not met:
+
+$LIBOSMOCORE_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBOSMOCORE_CFLAGS
+and LIBOSMOCORE_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details." "$LINENO" 5
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBOSMOCORE_CFLAGS
+and LIBOSMOCORE_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details" "$LINENO" 5 ; }
+else
+	LIBOSMOCORE_CFLAGS=$pkg_cv_LIBOSMOCORE_CFLAGS
+	LIBOSMOCORE_LIBS=$pkg_cv_LIBOSMOCORE_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+fi
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBOSMOVTY" >&5
+$as_echo_n "checking for LIBOSMOVTY... " >&6; }
+
+if test -n "$LIBOSMOVTY_CFLAGS"; then
+    pkg_cv_LIBOSMOVTY_CFLAGS="$LIBOSMOVTY_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmovty >= 0.1.28\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmovty >= 0.1.28") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOVTY_CFLAGS=`$PKG_CONFIG --cflags "libosmovty >= 0.1.28" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$LIBOSMOVTY_LIBS"; then
+    pkg_cv_LIBOSMOVTY_LIBS="$LIBOSMOVTY_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libosmovty >= 0.1.28\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libosmovty >= 0.1.28") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBOSMOVTY_LIBS=`$PKG_CONFIG --libs "libosmovty >= 0.1.28" 2>/dev/null`
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBOSMOVTY_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "libosmovty >= 0.1.28" 2>&1`
+        else
+	        LIBOSMOVTY_PKG_ERRORS=`$PKG_CONFIG --print-errors "libosmovty >= 0.1.28" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBOSMOVTY_PKG_ERRORS" >&5
+
+	as_fn_error $? "Package requirements (libosmovty >= 0.1.28) were not met:
+
+$LIBOSMOVTY_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+Alternatively, you may set the environment variables LIBOSMOVTY_CFLAGS
+and LIBOSMOVTY_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details." "$LINENO" 5
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+Alternatively, you may set the environment variables LIBOSMOVTY_CFLAGS
+and LIBOSMOVTY_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.
+See \`config.log' for more details" "$LINENO" 5 ; }
+else
+	LIBOSMOVTY_CFLAGS=$pkg_cv_LIBOSMOVTY_CFLAGS
+	LIBOSMOVTY_LIBS=$pkg_cv_LIBOSMOVTY_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if test "${ac_cv_prog_CPP+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5 ; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if test "${ac_cv_path_GREP+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in grep ggrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if test "${ac_cv_path_EGREP+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in egrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if test "${ac_cv_header_stdc+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_header_stdc=yes
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then :
+  :
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+		   (('a' <= (c) && (c) <= 'i') \
+		     || ('j' <= (c) && (c) <= 'r') \
+		     || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+	|| toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+		  inttypes.h stdint.h unistd.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_header in dahdi/user.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "dahdi/user.h" "ac_cv_header_dahdi_user_h" "$ac_includes_default"
+if test "x$ac_cv_header_dahdi_user_h" = x""yes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_DAHDI_USER_H 1
+_ACEOF
+
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: DAHDI input driver will not be built" >&5
+$as_echo "$as_me: WARNING: DAHDI input driver will not be built" >&2;}
+fi
+
+done
+
+
+
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if ${CC} supports -fvisibility=hidden" >&5
+$as_echo_n "checking if ${CC} supports -fvisibility=hidden... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+char foo;
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+   { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+        SYMBOL_VISIBILITY="-fvisibility=hidden"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+CFLAGS="$saved_CFLAGS"
+
+
+# Coverage build taken from WebKit's configure.in
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable code coverage support" >&5
+$as_echo_n "checking whether to enable code coverage support... " >&6; }
+# Check whether --enable-coverage was given.
+if test "${enable_coverage+set}" = set; then :
+  enableval=$enable_coverage;
+else
+  enable_coverage="no"
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_coverage" >&5
+$as_echo "$enable_coverage" >&6; }
+if test "$enable_coverage" = "yes"; then
+   COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs"
+   COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs"
+
+
+fi
+
+
+ac_config_headers="$ac_config_headers bscconfig.h"
+
+
+ac_config_files="$ac_config_files openbsc.pc include/openbsc/Makefile include/Makefile src/Makefile src/libtrau/Makefile src/libabis/Makefile src/libbsc/Makefile src/libmsc/Makefile src/libmgcp/Makefile src/libcommon/Makefile src/osmo-nitb/Makefile src/osmo-bsc/Makefile src/osmo-bsc_nat/Makefile src/osmo-bsc_mgcp/Makefile src/ipaccess/Makefile src/utils/Makefile src/libgb/Makefile src/gprs/Makefile tests/Makefile tests/debug/Makefile tests/gsm0408/Makefile tests/db/Makefile tests/channel/Makefile tests/bsc-nat/Makefile tests/mgcp/Makefile Makefile"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+	"s/'/'\\\\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    test "x$cache_file" != "x/dev/null" &&
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+    cat confcache >$cache_file
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+ if test -n "$EXEEXT"; then
+  am__EXEEXT_TRUE=
+  am__EXEEXT_FALSE='#'
+else
+  am__EXEEXT_TRUE='#'
+  am__EXEEXT_FALSE=
+fi
+
+if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then
+  as_fn_error $? "conditional \"AMDEP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
+  as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${HAVE_LIBGTP_TRUE}" && test -z "${HAVE_LIBGTP_FALSE}"; then
+  as_fn_error $? "conditional \"HAVE_LIBGTP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${BUILD_NAT_TRUE}" && test -z "${BUILD_NAT_FALSE}"; then
+  as_fn_error $? "conditional \"BUILD_NAT\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${BUILD_BSC_TRUE}" && test -z "${BUILD_BSC_FALSE}"; then
+  as_fn_error $? "conditional \"BUILD_BSC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+
+: ${CONFIG_STATUS=./config.status}
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -p'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -p'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -p'
+  fi
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+	test -d "$1/.";
+      else
+	case $1 in #(
+	-*)set "./$1";;
+	esac;
+	case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
+	???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by openbsc $as_me 0.9.13, which was
+generated by GNU Autoconf 2.67.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+      --header=FILE[:TEMPLATE]
+                   instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Configuration commands:
+$config_commands
+
+Report bugs to <openbsc-devel@lists.openbsc.org>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+openbsc config.status 0.9.13
+configured by $0, generated by GNU Autoconf 2.67,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2010 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h)
+    # Conflict between --help and --header
+    as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
+    "bscconfig.h") CONFIG_HEADERS="$CONFIG_HEADERS bscconfig.h" ;;
+    "openbsc.pc") CONFIG_FILES="$CONFIG_FILES openbsc.pc" ;;
+    "include/openbsc/Makefile") CONFIG_FILES="$CONFIG_FILES include/openbsc/Makefile" ;;
+    "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
+    "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
+    "src/libtrau/Makefile") CONFIG_FILES="$CONFIG_FILES src/libtrau/Makefile" ;;
+    "src/libabis/Makefile") CONFIG_FILES="$CONFIG_FILES src/libabis/Makefile" ;;
+    "src/libbsc/Makefile") CONFIG_FILES="$CONFIG_FILES src/libbsc/Makefile" ;;
+    "src/libmsc/Makefile") CONFIG_FILES="$CONFIG_FILES src/libmsc/Makefile" ;;
+    "src/libmgcp/Makefile") CONFIG_FILES="$CONFIG_FILES src/libmgcp/Makefile" ;;
+    "src/libcommon/Makefile") CONFIG_FILES="$CONFIG_FILES src/libcommon/Makefile" ;;
+    "src/osmo-nitb/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-nitb/Makefile" ;;
+    "src/osmo-bsc/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-bsc/Makefile" ;;
+    "src/osmo-bsc_nat/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-bsc_nat/Makefile" ;;
+    "src/osmo-bsc_mgcp/Makefile") CONFIG_FILES="$CONFIG_FILES src/osmo-bsc_mgcp/Makefile" ;;
+    "src/ipaccess/Makefile") CONFIG_FILES="$CONFIG_FILES src/ipaccess/Makefile" ;;
+    "src/utils/Makefile") CONFIG_FILES="$CONFIG_FILES src/utils/Makefile" ;;
+    "src/libgb/Makefile") CONFIG_FILES="$CONFIG_FILES src/libgb/Makefile" ;;
+    "src/gprs/Makefile") CONFIG_FILES="$CONFIG_FILES src/gprs/Makefile" ;;
+    "tests/Makefile") CONFIG_FILES="$CONFIG_FILES tests/Makefile" ;;
+    "tests/debug/Makefile") CONFIG_FILES="$CONFIG_FILES tests/debug/Makefile" ;;
+    "tests/gsm0408/Makefile") CONFIG_FILES="$CONFIG_FILES tests/gsm0408/Makefile" ;;
+    "tests/db/Makefile") CONFIG_FILES="$CONFIG_FILES tests/db/Makefile" ;;
+    "tests/channel/Makefile") CONFIG_FILES="$CONFIG_FILES tests/channel/Makefile" ;;
+    "tests/bsc-nat/Makefile") CONFIG_FILES="$CONFIG_FILES tests/bsc-nat/Makefile" ;;
+    "tests/mgcp/Makefile") CONFIG_FILES="$CONFIG_FILES tests/mgcp/Makefile" ;;
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+  test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp=
+  trap 'exit_status=$?
+  { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -n "$tmp" && test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = ""
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[	 ]*VPATH[	 ]*=[	 ]*/{
+h
+s///
+s/^/:/
+s/[	 ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[	 ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[	 ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+  ac_t=`sed -n "/$ac_delim/p" confdefs.h`
+  if test -z "$ac_t"; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any.  Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[	 ]*#[	 ]*define[	 ][	 ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[	 ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[	 ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[	 ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[	 ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  for (key in D) D_is_set[key] = 1
+  FS = ""
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+  line = \$ 0
+  split(line, arg, " ")
+  if (arg[1] == "#") {
+    defundef = arg[2]
+    mac1 = arg[3]
+  } else {
+    defundef = substr(arg[1], 2)
+    mac1 = arg[2]
+  }
+  split(mac1, mac2, "(") #)
+  macro = mac2[1]
+  prefix = substr(line, 1, index(line, defundef) - 1)
+  if (D_is_set[macro]) {
+    # Preserve the white space surrounding the "#".
+    print prefix "define", macro P[macro] D[macro]
+    next
+  } else {
+    # Replace #undef with comments.  This is necessary, for example,
+    # in the case of _POSIX_SOURCE, which is predefined and required
+    # on some systems where configure will not decide to define it.
+    if (defundef == "undef") {
+      print "/*", prefix defundef, macro, "*/"
+      next
+    }
+  }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+  as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X "  :F $CONFIG_FILES  :H $CONFIG_HEADERS    :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+	 # (if the path is not absolute).  The absolute path cannot be DOS-style,
+	 # because $ac_f cannot contain `:'.
+	 test -f "$ac_f" ||
+	   case $ac_f in
+	   [\\/$]*) false;;
+	   *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+	   esac ||
+	   as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+	  $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+	`' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5  ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$ac_file" : 'X\(//\)[^/]' \| \
+	 X"$ac_file" : 'X\(//\)$' \| \
+	 X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+  case $INSTALL in
+  [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+  *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+  esac
+  ac_MKDIR_P=$MKDIR_P
+  case $MKDIR_P in
+  [\\/$]* | ?:[\\/]* ) ;;
+  */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+  esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[	 ]*datarootdir[	 ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$tmp/stdin"
+  case $ac_file in
+  -) cat "$tmp/out" && rm -f "$tmp/out";;
+  *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+  :H)
+  #
+  # CONFIG_HEADER
+  #
+  if test x"$ac_file" != x-; then
+    {
+      $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs"
+    } >"$tmp/config.h" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    if diff "$ac_file" "$tmp/config.h" >/dev/null 2>&1; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+    else
+      rm -f "$ac_file"
+      mv "$tmp/config.h" "$ac_file" \
+	|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    fi
+  else
+    $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" \
+      || as_fn_error $? "could not create -" "$LINENO" 5
+  fi
+# Compute "$ac_file"'s index in $config_headers.
+_am_arg="$ac_file"
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" ||
+$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$_am_arg" : 'X\(//\)[^/]' \| \
+	 X"$_am_arg" : 'X\(//\)$' \| \
+	 X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$_am_arg" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`/stamp-h$_am_stamp_count
+ ;;
+
+  :C)  { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+$as_echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+  esac
+
+
+  case $ac_file$ac_mode in
+    "depfiles":C) test x"$AMDEP_TRUE" != x"" || {
+  # Autoconf 2.62 quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named `Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`$as_dirname -- "$mf" ||
+$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$mf" : 'X\(//\)[^/]' \| \
+	 X"$mf" : 'X\(//\)$' \| \
+	 X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$mf" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running `make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # When using ansi2knr, U may be empty or an underscore; expand it
+    U=`sed -n 's/^U = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+	 sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`$as_dirname -- "$file" ||
+$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$file" : 'X\(//\)[^/]' \| \
+	 X"$file" : 'X\(//\)$' \| \
+	 X"$file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      as_dir=$dirpart/$fdir; as_fn_mkdir_p
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+ ;;
+
+  esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..1d2db88
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,111 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([openbsc],
+	m4_esyscmd([./git-version-gen .tarball-version]),
+	[openbsc-devel@lists.openbsc.org])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+
+dnl checks for libraries
+AC_SEARCH_LIBS(crypt, crypt,
+    [LIBCRYPT="-lcrypt"; AC_DEFINE([VTY_CRYPT_PW], [], [Use crypt functionality of vty.])])
+AC_SEARCH_LIBS(gtp_new, gtp,
+    [LIBCRYPT="-lgtp"; AC_SUBST([GPRS_LIBGTP], [1])])
+
+AM_CONDITIONAL(HAVE_LIBGTP, test "x$GPRS_LIBGTP" != "x")
+
+
+AC_ARG_ENABLE([nat], [AS_HELP_STRING([--enable-nat], [Build the BSC NAT. Requires SCCP])],
+    [
+        PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.0.2)
+        osmo_ac_build_nat="yes"
+    ],
+    [
+        osmo_ac_build_nat="no"
+    ])
+AM_CONDITIONAL(BUILD_NAT, test "x$osmo_ac_build_nat" = "xyes")
+
+AC_ARG_ENABLE([osmo-bsc], [AS_HELP_STRING([--enable-osmo-bsc], [Build the Osmo BSC])],
+    [
+        PKG_CHECK_MODULES(LIBOSMOSCCP, libosmo-sccp >= 0.0.2)
+        osmo_ac_build_bsc="yes"
+    ],
+    [
+        osmo_ac_build_bsc="no"
+    ])
+AM_CONDITIONAL(BUILD_BSC, test "x$osmo_ac_build_bsc" = "xyes")
+
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.1.30)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.1.28)
+
+dnl checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS(dahdi/user.h,,AC_MSG_WARN(DAHDI input driver will not be built))
+
+
+dnl Checks for typedefs, structures and compiler characteristics
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([char foo;],
+      [ AC_MSG_RESULT([yes])
+        SYMBOL_VISIBILITY="-fvisibility=hidden"],
+        AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+# Coverage build taken from WebKit's configure.in
+AC_MSG_CHECKING([whether to enable code coverage support])
+AC_ARG_ENABLE(coverage,
+              AC_HELP_STRING([--enable-coverage],
+                             [enable code coverage support [default=no]]),
+              [],[enable_coverage="no"])
+AC_MSG_RESULT([$enable_coverage])
+if test "$enable_coverage" = "yes"; then
+   COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs"
+   COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs"
+   AC_SUBST([COVERAGE_CFLAGS])
+   AC_SUBST([COVERAGE_LDFLAGS])
+fi
+
+
+dnl Generate the output
+AM_CONFIG_HEADER(bscconfig.h)
+
+AC_OUTPUT(
+    openbsc.pc
+    include/openbsc/Makefile
+    include/Makefile
+    src/Makefile
+    src/libtrau/Makefile
+    src/libabis/Makefile
+    src/libbsc/Makefile
+    src/libmsc/Makefile
+    src/libmgcp/Makefile
+    src/libcommon/Makefile
+    src/osmo-nitb/Makefile
+    src/osmo-bsc/Makefile
+    src/osmo-bsc_nat/Makefile
+    src/osmo-bsc_mgcp/Makefile
+    src/ipaccess/Makefile
+    src/utils/Makefile
+    src/libgb/Makefile
+    src/gprs/Makefile
+    tests/Makefile
+    tests/debug/Makefile
+    tests/gsm0408/Makefile
+    tests/db/Makefile
+    tests/channel/Makefile
+    tests/bsc-nat/Makefile
+    tests/mgcp/Makefile
+    Makefile)
diff --git a/depcomp b/depcomp
new file mode 100755
index 0000000..df8eea7
--- /dev/null
+++ b/depcomp
@@ -0,0 +1,630 @@
+#! /bin/sh
+# depcomp - compile a program generating dependencies as side-effects
+
+scriptversion=2009-04-28.21; # UTC
+
+# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009 Free
+# Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+case $1 in
+  '')
+     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: depcomp [--help] [--version] PROGRAM [ARGS]
+
+Run PROGRAMS ARGS to compile a file, generating dependencies
+as side-effects.
+
+Environment variables:
+  depmode     Dependency tracking mode.
+  source      Source file read by `PROGRAMS ARGS'.
+  object      Object file output by `PROGRAMS ARGS'.
+  DEPDIR      directory where to store dependencies.
+  depfile     Dependency file to output.
+  tmpdepfile  Temporary file to use when outputing dependencies.
+  libtool     Whether libtool is used (yes/no).
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit $?
+    ;;
+  -v | --v*)
+    echo "depcomp $scriptversion"
+    exit $?
+    ;;
+esac
+
+if test -z "$depmode" || test -z "$source" || test -z "$object"; then
+  echo "depcomp: Variables source, object and depmode must be set" 1>&2
+  exit 1
+fi
+
+# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
+depfile=${depfile-`echo "$object" |
+  sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
+tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
+
+rm -f "$tmpdepfile"
+
+# Some modes work just like other modes, but use different flags.  We
+# parameterize here, but still list the modes in the big case below,
+# to make depend.m4 easier to write.  Note that we *cannot* use a case
+# here, because this file can only contain one case statement.
+if test "$depmode" = hp; then
+  # HP compiler uses -M and no extra arg.
+  gccflag=-M
+  depmode=gcc
+fi
+
+if test "$depmode" = dashXmstdout; then
+   # This is just like dashmstdout with a different argument.
+   dashmflag=-xM
+   depmode=dashmstdout
+fi
+
+cygpath_u="cygpath -u -f -"
+if test "$depmode" = msvcmsys; then
+   # This is just like msvisualcpp but w/o cygpath translation.
+   # Just convert the backslash-escaped backslashes to single forward
+   # slashes to satisfy depend.m4
+   cygpath_u="sed s,\\\\\\\\,/,g"
+   depmode=msvisualcpp
+fi
+
+case "$depmode" in
+gcc3)
+## gcc 3 implements dependency tracking that does exactly what
+## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
+## it if -MD -MP comes after the -MF stuff.  Hmm.
+## Unfortunately, FreeBSD c89 acceptance of flags depends upon
+## the command line argument order; so add the flags where they
+## appear in depend2.am.  Note that the slowdown incurred here
+## affects only configure: in makefiles, %FASTDEP% shortcuts this.
+  for arg
+  do
+    case $arg in
+    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
+    *)  set fnord "$@" "$arg" ;;
+    esac
+    shift # fnord
+    shift # $arg
+  done
+  "$@"
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  mv "$tmpdepfile" "$depfile"
+  ;;
+
+gcc)
+## There are various ways to get dependency output from gcc.  Here's
+## why we pick this rather obscure method:
+## - Don't want to use -MD because we'd like the dependencies to end
+##   up in a subdir.  Having to rename by hand is ugly.
+##   (We might end up doing this anyway to support other compilers.)
+## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
+##   -MM, not -M (despite what the docs say).
+## - Using -M directly means running the compiler twice (even worse
+##   than renaming).
+  if test -z "$gccflag"; then
+    gccflag=-MD,
+  fi
+  "$@" -Wp,"$gccflag$tmpdepfile"
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+## The second -e expression handles DOS-style file names with drive letters.
+  sed -e 's/^[^:]*: / /' \
+      -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
+## This next piece of magic avoids the `deleted header file' problem.
+## The problem is that when a header file which appears in a .P file
+## is deleted, the dependency causes make to die (because there is
+## typically no way to rebuild the header).  We avoid this by adding
+## dummy dependencies for each header file.  Too bad gcc doesn't do
+## this for us directly.
+  tr ' ' '
+' < "$tmpdepfile" |
+## Some versions of gcc put a space before the `:'.  On the theory
+## that the space means something, we add a space to the output as
+## well.
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+sgi)
+  if test "$libtool" = yes; then
+    "$@" "-Wp,-MDupdate,$tmpdepfile"
+  else
+    "$@" -MDupdate "$tmpdepfile"
+  fi
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+
+  if test -f "$tmpdepfile"; then  # yes, the sourcefile depend on other files
+    echo "$object : \\" > "$depfile"
+
+    # Clip off the initial element (the dependent).  Don't try to be
+    # clever and replace this with sed code, as IRIX sed won't handle
+    # lines with more than a fixed number of characters (4096 in
+    # IRIX 6.2 sed, 8192 in IRIX 6.5).  We also remove comment lines;
+    # the IRIX cc adds comments like `#:fec' to the end of the
+    # dependency line.
+    tr ' ' '
+' < "$tmpdepfile" \
+    | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \
+    tr '
+' ' ' >> "$depfile"
+    echo >> "$depfile"
+
+    # The second pass generates a dummy entry for each header file.
+    tr ' ' '
+' < "$tmpdepfile" \
+   | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
+   >> "$depfile"
+  else
+    # The sourcefile does not contain any dependencies, so just
+    # store a dummy comment line, to avoid errors with the Makefile
+    # "include basename.Plo" scheme.
+    echo "#dummy" > "$depfile"
+  fi
+  rm -f "$tmpdepfile"
+  ;;
+
+aix)
+  # The C for AIX Compiler uses -M and outputs the dependencies
+  # in a .u file.  In older versions, this file always lives in the
+  # current directory.  Also, the AIX compiler puts `$object:' at the
+  # start of each line; $object doesn't have directory information.
+  # Version 6 uses the directory in both cases.
+  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
+  test "x$dir" = "x$object" && dir=
+  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$base.u
+    tmpdepfile3=$dir.libs/$base.u
+    "$@" -Wc,-M
+  else
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$dir$base.u
+    tmpdepfile3=$dir$base.u
+    "$@" -M
+  fi
+  stat=$?
+
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  if test -f "$tmpdepfile"; then
+    # Each line is of the form `foo.o: dependent.h'.
+    # Do two passes, one to just change these to
+    # `$object: dependent.h' and one to simply `dependent.h:'.
+    sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
+    # That's a tab and a space in the [].
+    sed -e 's,^.*\.[a-z]*:[	 ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
+  else
+    # The sourcefile does not contain any dependencies, so just
+    # store a dummy comment line, to avoid errors with the Makefile
+    # "include basename.Plo" scheme.
+    echo "#dummy" > "$depfile"
+  fi
+  rm -f "$tmpdepfile"
+  ;;
+
+icc)
+  # Intel's C compiler understands `-MD -MF file'.  However on
+  #    icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c
+  # ICC 7.0 will fill foo.d with something like
+  #    foo.o: sub/foo.c
+  #    foo.o: sub/foo.h
+  # which is wrong.  We want:
+  #    sub/foo.o: sub/foo.c
+  #    sub/foo.o: sub/foo.h
+  #    sub/foo.c:
+  #    sub/foo.h:
+  # ICC 7.1 will output
+  #    foo.o: sub/foo.c sub/foo.h
+  # and will wrap long lines using \ :
+  #    foo.o: sub/foo.c ... \
+  #     sub/foo.h ... \
+  #     ...
+
+  "$@" -MD -MF "$tmpdepfile"
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  # Each line is of the form `foo.o: dependent.h',
+  # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
+  # Do two passes, one to just change these to
+  # `$object: dependent.h' and one to simply `dependent.h:'.
+  sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process this invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" |
+    sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp2)
+  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
+  # compilers, which have integrated preprocessors.  The correct option
+  # to use with these is +Maked; it writes dependencies to a file named
+  # 'foo.d', which lands next to the object file, wherever that
+  # happens to be.
+  # Much of this is similar to the tru64 case; see comments there.
+  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
+  test "x$dir" = "x$object" && dir=
+  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir.libs/$base.d
+    "$@" -Wc,+Maked
+  else
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir$base.d
+    "$@" +Maked
+  fi
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+     rm -f "$tmpdepfile1" "$tmpdepfile2"
+     exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  if test -f "$tmpdepfile"; then
+    sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile"
+    # Add `dependent.h:' lines.
+    sed -ne '2,${
+	       s/^ *//
+	       s/ \\*$//
+	       s/$/:/
+	       p
+	     }' "$tmpdepfile" >> "$depfile"
+  else
+    echo "#dummy" > "$depfile"
+  fi
+  rm -f "$tmpdepfile" "$tmpdepfile2"
+  ;;
+
+tru64)
+   # The Tru64 compiler uses -MD to generate dependencies as a side
+   # effect.  `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
+   # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
+   # dependencies in `foo.d' instead, so we check for that too.
+   # Subdirectories are respected.
+   dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
+   test "x$dir" = "x$object" && dir=
+   base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+
+   if test "$libtool" = yes; then
+      # With Tru64 cc, shared objects can also be used to make a
+      # static library.  This mechanism is used in libtool 1.4 series to
+      # handle both shared and static libraries in a single compilation.
+      # With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d.
+      #
+      # With libtool 1.5 this exception was removed, and libtool now
+      # generates 2 separate objects for the 2 libraries.  These two
+      # compilations output dependencies in $dir.libs/$base.o.d and
+      # in $dir$base.o.d.  We have to check for both files, because
+      # one of the two compilations can be disabled.  We should prefer
+      # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
+      # automatically cleaned when .libs/ is deleted, while ignoring
+      # the former would cause a distcleancheck panic.
+      tmpdepfile1=$dir.libs/$base.lo.d   # libtool 1.4
+      tmpdepfile2=$dir$base.o.d          # libtool 1.5
+      tmpdepfile3=$dir.libs/$base.o.d    # libtool 1.5
+      tmpdepfile4=$dir.libs/$base.d      # Compaq CCC V6.2-504
+      "$@" -Wc,-MD
+   else
+      tmpdepfile1=$dir$base.o.d
+      tmpdepfile2=$dir$base.d
+      tmpdepfile3=$dir$base.d
+      tmpdepfile4=$dir$base.d
+      "$@" -MD
+   fi
+
+   stat=$?
+   if test $stat -eq 0; then :
+   else
+      rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
+      exit $stat
+   fi
+
+   for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
+   do
+     test -f "$tmpdepfile" && break
+   done
+   if test -f "$tmpdepfile"; then
+      sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
+      # That's a tab and a space in the [].
+      sed -e 's,^.*\.[a-z]*:[	 ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
+   else
+      echo "#dummy" > "$depfile"
+   fi
+   rm -f "$tmpdepfile"
+   ;;
+
+#nosideeffect)
+  # This comment above is used by automake to tell side-effect
+  # dependency tracking mechanisms from slower ones.
+
+dashmstdout)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout, regardless of -o.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove `-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  test -z "$dashmflag" && dashmflag=-M
+  # Require at least two characters before searching for `:'
+  # in the target name.  This is to cope with DOS-style filenames:
+  # a dependency such as `c:/foo/bar' could be seen as target `c' otherwise.
+  "$@" $dashmflag |
+    sed 's:^[  ]*[^: ][^:][^:]*\:[    ]*:'"$object"'\: :' > "$tmpdepfile"
+  rm -f "$depfile"
+  cat < "$tmpdepfile" > "$depfile"
+  tr ' ' '
+' < "$tmpdepfile" | \
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+dashXmstdout)
+  # This case only exists to satisfy depend.m4.  It is never actually
+  # run, as this mode is specially recognized in the preamble.
+  exit 1
+  ;;
+
+makedepend)
+  "$@" || exit $?
+  # Remove any Libtool call
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+  # X makedepend
+  shift
+  cleared=no eat=no
+  for arg
+  do
+    case $cleared in
+    no)
+      set ""; shift
+      cleared=yes ;;
+    esac
+    if test $eat = yes; then
+      eat=no
+      continue
+    fi
+    case "$arg" in
+    -D*|-I*)
+      set fnord "$@" "$arg"; shift ;;
+    # Strip any option that makedepend may not understand.  Remove
+    # the object too, otherwise makedepend will parse it as a source file.
+    -arch)
+      eat=yes ;;
+    -*|$object)
+      ;;
+    *)
+      set fnord "$@" "$arg"; shift ;;
+    esac
+  done
+  obj_suffix=`echo "$object" | sed 's/^.*\././'`
+  touch "$tmpdepfile"
+  ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
+  rm -f "$depfile"
+  cat < "$tmpdepfile" > "$depfile"
+  sed '1,2d' "$tmpdepfile" | tr ' ' '
+' | \
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile" "$tmpdepfile".bak
+  ;;
+
+cpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove `-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  "$@" -E |
+    sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+       -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
+    sed '$ s: \\$::' > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  cat < "$tmpdepfile" >> "$depfile"
+  sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvisualcpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  IFS=" "
+  for arg
+  do
+    case "$arg" in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
+	set fnord "$@"
+	shift
+	shift
+	;;
+    *)
+	set fnord "$@" "$arg"
+	shift
+	shift
+	;;
+    esac
+  done
+  "$@" -E 2>/dev/null |
+  sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::	\1 \\:p' >> "$depfile"
+  echo "	" >> "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvcmsys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+none)
+  exec "$@"
+  ;;
+
+*)
+  echo "Unknown depmode $depmode" 1>&2
+  exit 1
+  ;;
+esac
+
+exit 0
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..4596b6e
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = openbsc
+
+noinst_HEADERS = mISDNif.h compat_af_isdn.h
diff --git a/include/Makefile.in b/include/Makefile.in
new file mode 100644
index 0000000..ecbeebe
--- /dev/null
+++ b/include/Makefile.in
@@ -0,0 +1,541 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = include
+DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+	$(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+	html-recursive info-recursive install-data-recursive \
+	install-dvi-recursive install-exec-recursive \
+	install-html-recursive install-info-recursive \
+	install-pdf-recursive install-ps-recursive install-recursive \
+	installcheck-recursive installdirs-recursive pdf-recursive \
+	ps-recursive uninstall-recursive
+HEADERS = $(noinst_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
+  distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+	$(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+	distdir
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = openbsc
+noinst_HEADERS = mISDNif.h compat_af_isdn.h
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu include/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+#     (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	target=`echo $@ | sed s/-recursive//`; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    dot_seen=yes; \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done; \
+	if test "$$dot_seen" = "no"; then \
+	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+	fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	case "$@" in \
+	  distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+	  *) list='$(SUBDIRS)' ;; \
+	esac; \
+	rev=''; for subdir in $$list; do \
+	  if test "$$subdir" = "."; then :; else \
+	    rev="$$subdir $$rev"; \
+	  fi; \
+	done; \
+	rev="$$rev ."; \
+	target=`echo $@ | sed s/-recursive//`; \
+	for subdir in $$rev; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done && test -z "$$fail"
+tags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+	done
+ctags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+	done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+	  include_option=--etags-include; \
+	  empty_fix=.; \
+	else \
+	  include_option=--include; \
+	  empty_fix=; \
+	fi; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test ! -f $$subdir/TAGS || \
+	      set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+	  fi; \
+	done; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test -d "$(distdir)/$$subdir" \
+	    || $(MKDIR_P) "$(distdir)/$$subdir" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+	    $(am__relativize); \
+	    new_distdir=$$reldir; \
+	    dir1=$$subdir; dir2="$(top_distdir)"; \
+	    $(am__relativize); \
+	    new_top_distdir=$$reldir; \
+	    echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+	    echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+	    ($(am__cd) $$subdir && \
+	      $(MAKE) $(AM_MAKEFLAGS) \
+	        top_distdir="$$new_top_distdir" \
+	        distdir="$$new_distdir" \
+		am__remove_distdir=: \
+		am__skip_length_check=: \
+		am__skip_mode_fix=: \
+	        distdir) \
+	      || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \
+	install-am install-strip tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+	all all-am check check-am clean clean-generic ctags \
+	ctags-recursive distclean distclean-generic distclean-tags \
+	distdir dvi dvi-am html html-am info info-am install \
+	install-am install-data install-data-am install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am install-man \
+	install-pdf install-pdf-am install-ps install-ps-am \
+	install-strip installcheck installcheck-am installdirs \
+	installdirs-am maintainer-clean maintainer-clean-generic \
+	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
+	tags-recursive uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/include/compat_af_isdn.h b/include/compat_af_isdn.h
new file mode 100644
index 0000000..56cbfb3
--- /dev/null
+++ b/include/compat_af_isdn.h
@@ -0,0 +1,39 @@
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+#undef AF_ISDN
+#undef PF_ISDN
+
+extern	int	AF_ISDN;
+#define PF_ISDN	AF_ISDN
+
+int	AF_ISDN;
+
+#endif
+
+extern void init_af_isdn(void);
+
+#ifdef AF_COMPATIBILITY_FUNC
+#ifdef MISDN_OLD_AF_COMPATIBILITY
+void init_af_isdn(void)
+{
+	int	s;
+
+	/* test for new value */
+	AF_ISDN = 34;
+	s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (s >= 0) {
+		close(s);
+		return;
+	}
+	AF_ISDN = 27;
+	s = socket(AF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (s >= 0) {
+		close(s);
+		return;
+	}
+}
+#else
+void init_af_isdn(void)
+{
+}
+#endif
+#endif
diff --git a/include/mISDNif.h b/include/mISDNif.h
new file mode 100644
index 0000000..8e065d2
--- /dev/null
+++ b/include/mISDNif.h
@@ -0,0 +1,387 @@
+/*
+ *
+ * Author	Karsten Keil <kkeil@novell.com>
+ *
+ * Copyright 2008  by Karsten Keil <kkeil@novell.com>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU LESSER GENERAL PUBLIC LICENSE for more details.
+ *
+ */
+
+#ifndef mISDNIF_H
+#define mISDNIF_H
+
+#include <stdarg.h>
+#ifdef linux
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#else
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#endif
+
+/*
+ * ABI Version 32 bit
+ *
+ * <8 bit> Major version
+ *		- changed if any interface become backwards incompatible
+ *
+ * <8 bit> Minor version
+ *              - changed if any interface is extended but backwards compatible
+ *
+ * <16 bit> Release number
+ *              - should be incremented on every checkin
+ */
+#define	MISDN_MAJOR_VERSION	1
+#define	MISDN_MINOR_VERSION	1
+#define MISDN_RELEASE		20
+
+/* primitives for information exchange
+ * generell format
+ * <16  bit  0 >
+ * <8  bit command>
+ *    BIT 8 = 1 LAYER private
+ *    BIT 7 = 1 answer
+ *    BIT 6 = 1 DATA
+ * <8  bit target layer mask>
+ *
+ * Layer = 00 is reserved for general commands
+   Layer = 01  L2 -> HW
+   Layer = 02  HW -> L2
+   Layer = 04  L3 -> L2
+   Layer = 08  L2 -> L3
+ * Layer = FF is reserved for broadcast commands
+ */
+
+#define MISDN_CMDMASK		0xff00
+#define MISDN_LAYERMASK		0x00ff
+
+/* generell commands */
+#define OPEN_CHANNEL		0x0100
+#define CLOSE_CHANNEL		0x0200
+#define CONTROL_CHANNEL		0x0300
+#define CHECK_DATA		0x0400
+
+/* layer 2 -> layer 1 */
+#define PH_ACTIVATE_REQ		0x0101
+#define PH_DEACTIVATE_REQ	0x0201
+#define PH_DATA_REQ		0x2001
+#define MPH_ACTIVATE_REQ	0x0501
+#define MPH_DEACTIVATE_REQ	0x0601
+#define MPH_INFORMATION_REQ	0x0701
+#define PH_CONTROL_REQ		0x0801
+
+/* layer 1 -> layer 2 */
+#define PH_ACTIVATE_IND		0x0102
+#define PH_ACTIVATE_CNF		0x4102
+#define PH_DEACTIVATE_IND	0x0202
+#define PH_DEACTIVATE_CNF	0x4202
+#define PH_DATA_IND		0x2002
+#define PH_DATA_E_IND		0x3002
+#define MPH_ACTIVATE_IND	0x0502
+#define MPH_DEACTIVATE_IND	0x0602
+#define MPH_INFORMATION_IND	0x0702
+#define PH_DATA_CNF		0x6002
+#define PH_CONTROL_IND		0x0802
+#define PH_CONTROL_CNF		0x4802
+
+/* layer 3 -> layer 2 */
+#define DL_ESTABLISH_REQ	0x1004
+#define DL_RELEASE_REQ		0x1104
+#define DL_DATA_REQ		0x3004
+#define DL_UNITDATA_REQ		0x3104
+#define DL_INFORMATION_REQ	0x0004
+
+/* layer 2 -> layer 3 */
+#define DL_ESTABLISH_IND	0x1008
+#define DL_ESTABLISH_CNF	0x5008
+#define DL_RELEASE_IND		0x1108
+#define DL_RELEASE_CNF		0x5108
+#define DL_DATA_IND		0x3008
+#define DL_UNITDATA_IND		0x3108
+#define DL_INFORMATION_IND	0x0008
+
+/* intern layer 2 managment */
+#define MDL_ASSIGN_REQ		0x1804
+#define MDL_ASSIGN_IND		0x1904
+#define MDL_REMOVE_REQ		0x1A04
+#define MDL_REMOVE_IND		0x1B04
+#define MDL_STATUS_UP_IND	0x1C04
+#define MDL_STATUS_DOWN_IND	0x1D04
+#define MDL_STATUS_UI_IND	0x1E04
+#define MDL_ERROR_IND		0x1F04
+#define MDL_ERROR_RSP		0x5F04
+
+/* DL_INFORMATION_IND types */
+#define DL_INFO_L2_CONNECT	0x0001
+#define DL_INFO_L2_REMOVED	0x0002
+
+/* PH_CONTROL types */
+/* TOUCH TONE IS 0x20XX  XX "0"..."9", "A","B","C","D","*","#" */
+#define DTMF_TONE_VAL		0x2000
+#define DTMF_TONE_MASK		0x007F
+#define DTMF_TONE_START		0x2100
+#define DTMF_TONE_STOP		0x2200
+#define DTMF_HFC_COEF		0x4000
+#define DSP_CONF_JOIN		0x2403
+#define DSP_CONF_SPLIT		0x2404
+#define DSP_RECEIVE_OFF		0x2405
+#define DSP_RECEIVE_ON		0x2406
+#define DSP_ECHO_ON		0x2407
+#define DSP_ECHO_OFF		0x2408
+#define DSP_MIX_ON		0x2409
+#define DSP_MIX_OFF		0x240a
+#define DSP_DELAY		0x240b
+#define DSP_JITTER		0x240c
+#define DSP_TXDATA_ON		0x240d
+#define DSP_TXDATA_OFF		0x240e
+#define DSP_TX_DEJITTER		0x240f
+#define DSP_TX_DEJ_OFF		0x2410
+#define DSP_TONE_PATT_ON	0x2411
+#define DSP_TONE_PATT_OFF	0x2412
+#define DSP_VOL_CHANGE_TX	0x2413
+#define DSP_VOL_CHANGE_RX	0x2414
+#define DSP_BF_ENABLE_KEY	0x2415
+#define DSP_BF_DISABLE		0x2416
+#define DSP_BF_ACCEPT		0x2416
+#define DSP_BF_REJECT		0x2417
+#define DSP_PIPELINE_CFG	0x2418
+#define HFC_VOL_CHANGE_TX	0x2601
+#define HFC_VOL_CHANGE_RX	0x2602
+#define HFC_SPL_LOOP_ON		0x2603
+#define HFC_SPL_LOOP_OFF	0x2604
+
+/* DSP_TONE_PATT_ON parameter */
+#define TONE_OFF			0x0000
+#define TONE_GERMAN_DIALTONE		0x0001
+#define TONE_GERMAN_OLDDIALTONE		0x0002
+#define TONE_AMERICAN_DIALTONE		0x0003
+#define TONE_GERMAN_DIALPBX		0x0004
+#define TONE_GERMAN_OLDDIALPBX		0x0005
+#define TONE_AMERICAN_DIALPBX		0x0006
+#define TONE_GERMAN_RINGING		0x0007
+#define TONE_GERMAN_OLDRINGING		0x0008
+#define TONE_AMERICAN_RINGPBX		0x000b
+#define TONE_GERMAN_RINGPBX		0x000c
+#define TONE_GERMAN_OLDRINGPBX		0x000d
+#define TONE_AMERICAN_RINGING		0x000e
+#define TONE_GERMAN_BUSY		0x000f
+#define TONE_GERMAN_OLDBUSY		0x0010
+#define TONE_AMERICAN_BUSY		0x0011
+#define TONE_GERMAN_HANGUP		0x0012
+#define TONE_GERMAN_OLDHANGUP		0x0013
+#define TONE_AMERICAN_HANGUP		0x0014
+#define TONE_SPECIAL_INFO		0x0015
+#define TONE_GERMAN_GASSENBESETZT	0x0016
+#define TONE_GERMAN_AUFSCHALTTON	0x0016
+
+/* MPH_INFORMATION_IND */
+#define L1_SIGNAL_LOS_OFF	0x0010
+#define L1_SIGNAL_LOS_ON	0x0011
+#define L1_SIGNAL_AIS_OFF	0x0012
+#define L1_SIGNAL_AIS_ON	0x0013
+#define L1_SIGNAL_RDI_OFF	0x0014
+#define L1_SIGNAL_RDI_ON	0x0015
+#define L1_SIGNAL_SLIP_RX	0x0020
+#define L1_SIGNAL_SLIP_TX	0x0021
+
+/*
+ * protocol ids
+ * D channel 1-31
+ * B channel 33 - 63
+ */
+
+#define ISDN_P_NONE		0
+#define ISDN_P_BASE		0
+#define ISDN_P_TE_S0		0x01
+#define ISDN_P_NT_S0  		0x02
+#define ISDN_P_TE_E1		0x03
+#define ISDN_P_NT_E1  		0x04
+#define ISDN_P_TE_UP0		0x05
+#define ISDN_P_NT_UP0		0x06
+
+#define IS_ISDN_P_TE(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_TE_E1) || \
+				(p == ISDN_P_TE_UP0) || (p == ISDN_P_LAPD_TE))
+#define IS_ISDN_P_NT(p) ((p == ISDN_P_NT_S0) || (p == ISDN_P_NT_E1) || \
+				(p == ISDN_P_NT_UP0) || (p == ISDN_P_LAPD_NT))
+#define IS_ISDN_P_S0(p) ((p == ISDN_P_TE_S0) || (p == ISDN_P_NT_S0))
+#define IS_ISDN_P_E1(p) ((p == ISDN_P_TE_E1) || (p == ISDN_P_NT_E1))
+#define IS_ISDN_P_UP0(p) ((p == ISDN_P_TE_UP0) || (p == ISDN_P_NT_UP0))
+
+
+#define ISDN_P_LAPD_TE		0x10
+#define	ISDN_P_LAPD_NT		0x11
+
+#define ISDN_P_B_MASK		0x1f
+#define ISDN_P_B_START		0x20
+
+#define ISDN_P_B_RAW		0x21
+#define ISDN_P_B_HDLC		0x22
+#define ISDN_P_B_X75SLP		0x23
+#define ISDN_P_B_L2DTMF		0x24
+#define ISDN_P_B_L2DSP		0x25
+#define ISDN_P_B_L2DSPHDLC	0x26
+
+#define OPTION_L2_PMX		1
+#define OPTION_L2_PTP		2
+#define OPTION_L2_FIXEDTEI	3
+#define OPTION_L2_CLEANUP	4
+
+/* should be in sync with linux/kobject.h:KOBJ_NAME_LEN */
+#define MISDN_MAX_IDLEN		20
+
+struct mISDNhead {
+	unsigned int	prim;
+	unsigned int	id;
+}  __attribute__((packed));
+
+#define MISDN_HEADER_LEN	sizeof(struct mISDNhead)
+#define MAX_DATA_SIZE		2048
+#define MAX_DATA_MEM		(MAX_DATA_SIZE + MISDN_HEADER_LEN)
+#define MAX_DFRAME_LEN		260
+
+#define MISDN_ID_ADDR_MASK	0xFFFF
+#define MISDN_ID_TEI_MASK	0xFF00
+#define MISDN_ID_SAPI_MASK	0x00FF
+#define MISDN_ID_TEI_ANY	0x7F00
+
+#define MISDN_ID_ANY		0xFFFF
+#define MISDN_ID_NONE		0xFFFE
+
+#define GROUP_TEI		127
+#define TEI_SAPI		63
+#define CTRL_SAPI		0
+
+#define MISDN_MAX_CHANNEL	127
+#define MISDN_CHMAP_SIZE	((MISDN_MAX_CHANNEL + 1) >> 3)
+
+#define SOL_MISDN	0
+
+struct sockaddr_mISDN {
+	sa_family_t    family;
+	unsigned char	dev;
+	unsigned char	channel;
+	unsigned char	sapi;
+	unsigned char	tei;
+};
+
+struct mISDNversion {
+	unsigned char	major;
+	unsigned char	minor;
+	unsigned short	release;
+};
+
+#define MAX_DEVICE_ID 63
+
+struct mISDN_devinfo {
+	u_int			id;
+	u_int			Dprotocols;
+	u_int			Bprotocols;
+	u_int			protocol;
+	u_char			channelmap[MISDN_CHMAP_SIZE];
+	u_int			nrbchan;
+	char			name[MISDN_MAX_IDLEN];
+};
+
+struct mISDN_devrename {
+	u_int			id;
+	char			name[MISDN_MAX_IDLEN];
+};
+
+struct ph_info_ch {
+	int32_t 		protocol;
+	int64_t			Flags;
+};
+
+struct ph_info_dch {
+	struct ph_info_ch	ch;
+	int16_t			state;
+	int16_t			num_bch;
+};
+
+struct ph_info {
+	struct ph_info_dch	dch;
+	struct ph_info_ch 	bch[];
+};
+
+/* timer device ioctl */
+#define IMADDTIMER	_IOR('I', 64, int)
+#define IMDELTIMER	_IOR('I', 65, int)
+/* socket ioctls */
+#define	IMGETVERSION	_IOR('I', 66, int)
+#define	IMGETCOUNT	_IOR('I', 67, int)
+#define IMGETDEVINFO	_IOR('I', 68, int)
+#define IMCTRLREQ	_IOR('I', 69, int)
+#define IMCLEAR_L2	_IOR('I', 70, int)
+#define IMSETDEVNAME	_IOR('I', 71, struct mISDN_devrename)
+
+static inline int
+test_channelmap(u_int nr, u_char *map)
+{
+	if (nr <= MISDN_MAX_CHANNEL)
+		return map[nr >> 3] & (1 << (nr & 7));
+	else
+		return 0;
+}
+
+static inline void
+set_channelmap(u_int nr, u_char *map)
+{
+	map[nr >> 3] |= (1 << (nr & 7));
+}
+
+static inline void
+clear_channelmap(u_int nr, u_char *map)
+{
+	map[nr >> 3] &= ~(1 << (nr & 7));
+}
+
+/* CONTROL_CHANNEL parameters */
+#define MISDN_CTRL_GETOP		0x0000
+#define MISDN_CTRL_LOOP			0x0001
+#define MISDN_CTRL_CONNECT		0x0002
+#define MISDN_CTRL_DISCONNECT		0x0004
+#define MISDN_CTRL_PCMCONNECT		0x0010
+#define MISDN_CTRL_PCMDISCONNECT	0x0020
+#define MISDN_CTRL_SETPEER		0x0040
+#define MISDN_CTRL_UNSETPEER		0x0080
+#define MISDN_CTRL_RX_OFF		0x0100
+#define MISDN_CTRL_FILL_EMPTY		0x0200
+#define MISDN_CTRL_GETPEER		0x0400
+#define MISDN_CTRL_HW_FEATURES_OP	0x2000
+#define MISDN_CTRL_HW_FEATURES		0x2001
+#define MISDN_CTRL_HFC_OP		0x4000
+#define MISDN_CTRL_HFC_PCM_CONN		0x4001
+#define MISDN_CTRL_HFC_PCM_DISC		0x4002
+#define MISDN_CTRL_HFC_CONF_JOIN	0x4003
+#define MISDN_CTRL_HFC_CONF_SPLIT	0x4004
+#define MISDN_CTRL_HFC_RECEIVE_OFF	0x4005
+#define MISDN_CTRL_HFC_RECEIVE_ON	0x4006
+#define MISDN_CTRL_HFC_ECHOCAN_ON 	0x4007
+#define MISDN_CTRL_HFC_ECHOCAN_OFF 	0x4008
+
+
+/* socket options */
+#define MISDN_TIME_STAMP		0x0001
+
+struct mISDN_ctrl_req {
+	int		op;
+	int		channel;
+	int		p1;
+	int		p2;
+};
+
+/* muxer options */
+#define MISDN_OPT_ALL		1
+#define MISDN_OPT_TEIMGR	2
+
+#endif /* mISDNIF_H */
diff --git a/include/openbsc/Makefile.am b/include/openbsc/Makefile.am
new file mode 100644
index 0000000..325d66d
--- /dev/null
+++ b/include/openbsc/Makefile.am
@@ -0,0 +1,17 @@
+noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \
+		 gsm_subscriber.h gsm_04_11.h debug.h signal.h \
+		 misdn.h chan_alloc.h paging.h \
+		 subchan_demux.h trau_frame.h e1_input.h trau_mux.h \
+		 ipaccess.h rs232.h openbscdefines.h rtp_proxy.h \
+		 bsc_rll.h mncc.h transaction.h ussd.h gsm_04_80.h \
+		 silent_call.h mgcp.h meas_rep.h rest_octets.h \
+		 system_information.h handover.h mgcp_internal.h \
+		 vty.h socket.h \
+		crc24.h gprs_bssgp.h gprs_llc.h gprs_ns.h gprs_gmm.h \
+		gb_proxy.h gprs_sgsn.h gsm_04_08_gprs.h sgsn.h \
+		gprs_ns_frgre.h auth.h osmo_msc.h bsc_msc.h bsc_nat.h \
+		osmo_bsc_rf.h osmo_bsc.h network_listen.h bsc_nat_sccp.h \
+		osmo_msc_data.h osmo_bsc_grace.h sms_queue.h abis_om2000.h
+
+openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
+openbscdir = $(includedir)/openbsc
diff --git a/include/openbsc/Makefile.in b/include/openbsc/Makefile.in
new file mode 100644
index 0000000..faca5a3
--- /dev/null
+++ b/include/openbsc/Makefile.in
@@ -0,0 +1,449 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = include/openbsc
+DIST_COMMON = $(noinst_HEADERS) $(openbsc_HEADERS) \
+	$(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__installdirs = "$(DESTDIR)$(openbscdir)"
+HEADERS = $(noinst_HEADERS) $(openbsc_HEADERS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_HEADERS = abis_nm.h abis_rsl.h db.h gsm_04_08.h gsm_data.h \
+		 gsm_subscriber.h gsm_04_11.h debug.h signal.h \
+		 misdn.h chan_alloc.h paging.h \
+		 subchan_demux.h trau_frame.h e1_input.h trau_mux.h \
+		 ipaccess.h rs232.h openbscdefines.h rtp_proxy.h \
+		 bsc_rll.h mncc.h transaction.h ussd.h gsm_04_80.h \
+		 silent_call.h mgcp.h meas_rep.h rest_octets.h \
+		 system_information.h handover.h mgcp_internal.h \
+		 vty.h socket.h \
+		crc24.h gprs_bssgp.h gprs_llc.h gprs_ns.h gprs_gmm.h \
+		gb_proxy.h gprs_sgsn.h gsm_04_08_gprs.h sgsn.h \
+		gprs_ns_frgre.h auth.h osmo_msc.h bsc_msc.h bsc_nat.h \
+		osmo_bsc_rf.h osmo_bsc.h network_listen.h bsc_nat_sccp.h \
+		osmo_msc_data.h osmo_bsc_grace.h sms_queue.h abis_om2000.h
+
+openbsc_HEADERS = gsm_04_08.h meas_rep.h bsc_api.h
+openbscdir = $(includedir)/openbsc
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/openbsc/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu include/openbsc/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-openbscHEADERS: $(openbsc_HEADERS)
+	@$(NORMAL_INSTALL)
+	test -z "$(openbscdir)" || $(MKDIR_P) "$(DESTDIR)$(openbscdir)"
+	@list='$(openbsc_HEADERS)'; test -n "$(openbscdir)" || list=; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(openbscdir)'"; \
+	  $(INSTALL_HEADER) $$files "$(DESTDIR)$(openbscdir)" || exit $$?; \
+	done
+
+uninstall-openbscHEADERS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(openbsc_HEADERS)'; test -n "$(openbscdir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	test -n "$$files" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(openbscdir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(openbscdir)" && rm -f $$files
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(HEADERS)
+installdirs:
+	for dir in "$(DESTDIR)$(openbscdir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-openbscHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-openbscHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	ctags distclean distclean-generic distclean-tags distdir dvi \
+	dvi-am html html-am info info-am install install-am \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man \
+	install-openbscHEADERS install-pdf install-pdf-am install-ps \
+	install-ps-am install-strip installcheck installcheck-am \
+	installdirs maintainer-clean maintainer-clean-generic \
+	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
+	uninstall uninstall-am uninstall-openbscHEADERS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/include/openbsc/abis_nm.h b/include/openbsc/abis_nm.h
new file mode 100644
index 0000000..c93db58
--- /dev/null
+++ b/include/openbsc/abis_nm.h
@@ -0,0 +1,172 @@
+/* GSM Network Management messages on the A-bis interface 
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _NM_H
+#define _NM_H
+
+#include <sys/types.h>
+#include <osmocore/tlv.h>
+#include <osmocore/protocol/gsm_12_21.h>
+
+struct cell_global_id {
+	u_int16_t mcc;
+	u_int16_t mnc;
+	u_int16_t lac;
+	u_int16_t ci;
+};
+
+/* The BCCH info from an ip.access test, in host byte order
+ * and already parsed... */
+struct ipac_bcch_info {
+	struct llist_head list;
+
+	u_int16_t info_type;
+	u_int8_t freq_qual;
+	u_int16_t arfcn;
+	u_int8_t rx_lev;
+	u_int8_t rx_qual;
+	int16_t freq_err;
+	u_int16_t frame_offset;
+	u_int32_t frame_nr_offset;
+	u_int8_t bsic;
+	struct cell_global_id cgi;
+	u_int8_t ba_list_si2[16];
+	u_int8_t ba_list_si2bis[16];
+	u_int8_t ba_list_si2ter[16];
+	u_int8_t ca_list_si1[16];
+};
+
+extern const struct value_string abis_nm_adm_state_names[];
+extern const struct value_string abis_nm_obj_class_names[];
+extern const struct tlv_definition nm_att_tlvdef;
+
+/* PUBLIC */
+
+struct msgb;
+
+struct abis_nm_cfg {
+	/* callback for unidirectional reports */
+	int (*report_cb)(struct msgb *,
+			 struct abis_om_fom_hdr *);
+	/* callback for software activate requests from BTS */
+	int (*sw_act_req)(struct msgb *);
+};
+
+extern int abis_nm_rcvmsg(struct msgb *msg);
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_t *buf, int len);
+int abis_nm_rx(struct msgb *msg);
+int abis_nm_opstart(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2);
+int abis_nm_chg_adm_state(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0,
+			  u_int8_t i1, u_int8_t i2, enum abis_nm_adm_state adm_state);
+int abis_nm_establish_tei(struct gsm_bts *bts, u_int8_t trx_nr,
+			  u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei);
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+			   u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot);
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+			   u_int8_t e1_port, u_int8_t e1_timeslot,
+			   u_int8_t e1_subslot);
+int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len);
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, u_int8_t *attr, int attr_len);
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb);
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i1,
+			u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len);
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *msg);
+int abis_nm_event_reports(struct gsm_bts *bts, int on);
+int abis_nm_reset_resource(struct gsm_bts *bts);
+int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
+			  u_int8_t win_size, int forced,
+			  gsm_cbfn *cbfn, void *cb_data);
+int abis_nm_software_load_status(struct gsm_bts *bts);
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+			      gsm_cbfn *cbfn, void *cb_data);
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0,
+			    u_int8_t e1_port1, u_int8_t ts1);
+
+int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report, struct msgb *msg);
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan);
+
+/* Siemens / BS-11 specific */
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts);
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin);
+int abis_nm_bs11_create_object(struct gsm_bts *bts, enum abis_bs11_objtype type,
+			  u_int8_t idx, u_int8_t attr_len, const u_int8_t *attr);
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, u_int8_t idx);
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, u_int8_t idx);
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, u_int8_t idx);
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, u_int8_t idx);
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, u_int8_t e1_port,
+			  u_int8_t e1_timeslot, u_int8_t e1_subslot, u_int8_t tei);
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts);
+int abis_nm_bs11_get_serno(struct gsm_bts *bts);
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, u_int8_t level);
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx);
+int abis_nm_bs11_logon(struct gsm_bts *bts, u_int8_t level, const char *name, int on);
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on);
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password);
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked);
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts);
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value);
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts);
+int abis_nm_bs11_get_state(struct gsm_bts *bts);
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+			  u_int8_t win_size, int forced, gsm_cbfn *cbfn);
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts);
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport);
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport, enum abis_bs11_line_cfg line_cfg);
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect);
+int abis_nm_bs11_restart(struct gsm_bts *bts);
+
+/* ip.access nanoBTS specific commands */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, u_int8_t msg_type,
+			 u_int8_t obj_class, u_int8_t bts_nr,
+			 u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t *attr, int attr_len);
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr,
+				int attr_len);
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx);
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class,
+				u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+				u_int8_t *attr, u_int8_t attr_len);
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, 
+				 u_int32_t ip, u_int16_t port, u_int8_t stream);
+void abis_nm_ipaccess_cgi(u_int8_t *buf, struct gsm_bts *bts);
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, u_int8_t *buf);
+const char *ipacc_testres_name(u_int8_t res);
+
+/* Functions calling into other code parts */
+const char *nm_opstate_name(u_int8_t os);
+const char *nm_avail_name(u_int8_t avail);
+int nm_is_running(struct gsm_nm_state *s);
+
+int abis_nm_vty_init(void);
+
+void abis_nm_clear_queue(struct gsm_bts *bts);
+
+
+#endif /* _NM_H */
diff --git a/include/openbsc/abis_om2000.h b/include/openbsc/abis_om2000.h
new file mode 100644
index 0000000..e4f19cf
--- /dev/null
+++ b/include/openbsc/abis_om2000.h
@@ -0,0 +1,78 @@
+#ifndef OPENBSC_ABIS_OM2K_H
+#define OPENBSC_ABIS_OM2K_H
+/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface
+ * implemented based on protocol trace analysis, no formal documentation */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+enum abis_om2k_mo_cls {
+	OM2K_MO_CLS_TRXC			= 0x01,
+	OM2K_MO_CLS_TS				= 0x03,
+	OM2K_MO_CLS_TF				= 0x04,
+	OM2K_MO_CLS_IS				= 0x05,
+	OM2K_MO_CLS_CON				= 0x06,
+	OM2K_MO_CLS_DP				= 0x07,
+	OM2K_MO_CLS_CF				= 0x0a,
+	OM2K_MO_CLS_TX				= 0x0b,
+	OM2K_MO_CLS_RX				= 0x0c,
+};
+
+struct abis_om2k_mo {
+	uint8_t class;
+	uint8_t bts;
+	uint8_t assoc_so;
+	uint8_t inst;
+} __attribute__ ((packed));
+
+struct om2k_is_conn_grp {
+	uint16_t icp1;
+	uint16_t icp2;
+	uint8_t cont_idx;
+} __attribute__ ((packed));
+
+extern const struct value_string om2k_mo_class_short_vals[];
+
+int abis_om2k_rcvmsg(struct msgb *msg);
+
+extern const struct abis_om2k_mo om2k_mo_cf;
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			 uint8_t operational);
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts, struct om2k_is_conn_grp *cg,
+			     unsigned int num_cg);
+int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts);
+int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx);
+int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx);
+int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts);
+
+int abis_om2k_vty_init(void);
+
+struct vty;
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts);
+
+#endif /* OPENBCS_ABIS_OM2K_H */
diff --git a/include/openbsc/abis_rsl.h b/include/openbsc/abis_rsl.h
new file mode 100644
index 0000000..295b01f
--- /dev/null
+++ b/include/openbsc/abis_rsl.h
@@ -0,0 +1,99 @@
+/* GSM Radio Signalling Link messages on the A-bis interface 
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _RSL_H
+#define _RSL_H
+
+#include <osmocore/protocol/gsm_08_58.h>
+
+#include <osmocore/msgb.h>
+
+struct gsm_bts;
+struct gsm_lchan;
+struct gsm_subscriber;
+struct gsm_bts_trx_ts;
+
+
+int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type,
+		  const u_int8_t *data, int len);
+int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type, 
+		      const u_int8_t *data, int len);
+int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr,
+		      u_int8_t act_type,
+		      struct rsl_ie_chan_mode *chan_mode,
+		      struct rsl_ie_chan_ident *chan_ident,
+		      u_int8_t bs_power, u_int8_t ms_power,
+		      u_int8_t ta);
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type, 
+			    u_int8_t ta, u_int8_t ho_ref);
+int rsl_chan_mode_modify_req(struct gsm_lchan *ts);
+int rsl_encryption_cmd(struct msgb *msg);
+int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len,
+		   u_int8_t *ms_ident, u_int8_t chan_needed);
+int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val);
+
+int rsl_data_request(struct msgb *msg, u_int8_t link_id);
+int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id);
+int rsl_relase_request(struct gsm_lchan *lchan, u_int8_t link_id);
+
+/* Siemens vendor-specific RSL extensions */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci);
+
+/* ip.access specfic RSL extensions */
+int rsl_ipacc_crcx(struct gsm_lchan *lchan);
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip,
+		   u_int16_t port, u_int8_t rtp_payload2);
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan);
+int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act);
+
+int abis_rsl_rcvmsg(struct msgb *msg);
+
+unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			      int n_pag_blocks);
+unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res);
+u_int64_t str_to_imsi(const char *imsi_str);
+u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan);
+int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t reason);
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int);
+
+/* to be provided by external code */
+int abis_rsl_sendmsg(struct msgb *msg);
+int rsl_deact_sacch(struct gsm_lchan *lchan);
+int rsl_lchan_rll_release(struct gsm_lchan *lchan, u_int8_t link_id);
+
+/* BCCH related code */
+int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf);
+int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf);
+int rsl_number_of_paging_subchannels(struct gsm_bts *bts);
+
+int rsl_sacch_info_modify(struct gsm_lchan *lchan, u_int8_t type,
+			  const u_int8_t *data, int len);
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db);
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm);
+
+/* SMSCB functionality */
+int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
+		       uint8_t cb_command, const uint8_t *data, int len);
+
+#endif /* RSL_MT_H */
+
diff --git a/include/openbsc/auth.h b/include/openbsc/auth.h
new file mode 100644
index 0000000..2364fb3
--- /dev/null
+++ b/include/openbsc/auth.h
@@ -0,0 +1,17 @@
+#ifndef _AUTH_H
+#define _AUTH_H
+
+struct gsm_auth_tuple;
+struct gsm_subscriber;
+
+enum auth_action {
+	AUTH_NOT_AVAIL		= 0,	/* No auth tuple available */
+	AUTH_DO_AUTH_THAN_CIPH	= 1,	/* Firsth authenticate, then cipher */
+	AUTH_DO_CIPH		= 2,	/* Only ciphering */
+	AUTH_DO_AUTH		= 3,	/* Only authentication, no ciphering */
+};
+
+int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple,
+                              struct gsm_subscriber *subscr, int key_seq);
+
+#endif /* _AUTH_H */
diff --git a/include/openbsc/bsc_api.h b/include/openbsc/bsc_api.h
new file mode 100644
index 0000000..36ec370
--- /dev/null
+++ b/include/openbsc/bsc_api.h
@@ -0,0 +1,37 @@
+/* GSM 08.08 like API for OpenBSC */
+
+#ifndef OPENBSC_BSC_API_H
+#define OPENBSC_BSC_API_H
+
+#include "gsm_data.h"
+
+#define BSC_API_CONN_POL_ACCEPT	0
+#define BSC_API_CONN_POL_REJECT	1
+
+struct bsc_api {
+	void (*sapi_n_reject)(struct gsm_subscriber_connection *conn, int dlci);
+	void (*cipher_mode_compl)(struct gsm_subscriber_connection *conn,
+				  struct msgb *msg, uint8_t chosen_encr);
+	int (*compl_l3)(struct gsm_subscriber_connection *conn,
+			struct msgb *msg, uint16_t chosen_channel); 
+	void (*dtap)(struct gsm_subscriber_connection *conn, uint8_t link_id,
+			struct msgb *msg);
+	void (*assign_compl)(struct gsm_subscriber_connection *conn,
+			  uint8_t rr_cause, uint8_t chosen_channel,
+			  uint8_t encr_alg_id, uint8_t speech_mode);
+	void (*assign_fail)(struct gsm_subscriber_connection *conn,
+			 uint8_t cause, uint8_t *rr_cause);
+	int (*clear_request)(struct gsm_subscriber_connection *conn,
+			      uint32_t cause);
+};
+
+int bsc_api_init(struct gsm_network *network, struct bsc_api *api);
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, int link_id, int allow_sach);
+int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate);
+int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
+			const uint8_t *key, int len, int include_imeisv);
+int gsm0808_page(struct gsm_bts *bts, unsigned int page_group,
+		 unsigned int mi_len, uint8_t *mi, int chan_type);
+int gsm0808_clear(struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/include/openbsc/bsc_msc.h b/include/openbsc/bsc_msc.h
new file mode 100644
index 0000000..d06ae05
--- /dev/null
+++ b/include/openbsc/bsc_msc.h
@@ -0,0 +1,51 @@
+/* Routines to talk to the MSC using the IPA Protocol */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_MSC_H
+#define BSC_MSC_H
+
+#include <osmocore/write_queue.h>
+#include <osmocore/timer.h>
+
+struct bsc_msc_connection {
+	struct write_queue write_queue;
+	int is_connected;
+	int is_authenticated;
+	int first_contact;
+	const char *ip;
+	int port;
+	int prio;
+
+	void (*connection_loss) (struct bsc_msc_connection *);
+	void (*connected) (struct bsc_msc_connection *);
+	struct timer_list reconnect_timer;
+	struct timer_list timeout_timer;
+};
+
+struct bsc_msc_connection *bsc_msc_create(const char *ip, int port, int prio);
+int bsc_msc_connect(struct bsc_msc_connection *);
+void bsc_msc_schedule_connect(struct bsc_msc_connection *);
+
+void bsc_msc_lost(struct bsc_msc_connection *);
+
+struct msgb *bsc_msc_id_get_resp(const char *token);
+
+#endif
diff --git a/include/openbsc/bsc_nat.h b/include/openbsc/bsc_nat.h
new file mode 100644
index 0000000..f74cae2
--- /dev/null
+++ b/include/openbsc/bsc_nat.h
@@ -0,0 +1,355 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_NAT_H
+#define BSC_NAT_H
+
+#include "mgcp.h"
+
+#include <sys/types.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <osmocore/msgfile.h>
+#include <osmocore/timer.h>
+#include <osmocore/write_queue.h>
+#include <osmocore/rate_ctr.h>
+#include <osmocore/statistics.h>
+#include <osmocore/protocol/gsm_04_08.h>
+
+#include <regex.h>
+
+#define DIR_BSC 1
+#define DIR_MSC 2
+
+struct sccp_source_reference;
+struct sccp_connections;
+struct bsc_nat_parsed;
+struct bsc_nat;
+struct bsc_nat_ussd_con;
+
+enum {
+	NAT_CON_TYPE_NONE,
+	NAT_CON_TYPE_LU,
+	NAT_CON_TYPE_CM_SERV_REQ,
+	NAT_CON_TYPE_PAG_RESP,
+	NAT_CON_TYPE_SSA,
+	NAT_CON_TYPE_LOCAL_REJECT,
+	NAT_CON_TYPE_OTHER,
+};
+
+/*
+ * Per BSC data structure
+ */
+struct bsc_connection {
+	struct llist_head list_entry;
+
+	/* do we know anything about this BSC? */
+	int authenticated;
+
+	/* the fd we use to communicate */
+	struct write_queue write_queue;
+
+	/* the BSS associated */
+	struct bsc_config *cfg;
+
+	/* a timeout node */
+	struct timer_list id_timeout;
+
+	/* pong timeout */
+	struct timer_list ping_timeout;
+	struct timer_list pong_timeout;
+
+	/* mgcp related code */
+	char *_endpoint_status;
+	int number_multiplexes;
+	int max_endpoints;
+	int last_endpoint;
+
+	/* a back pointer */
+	struct bsc_nat *nat;
+};
+
+/**
+ * Stats per BSC
+ */
+struct bsc_config_stats {
+	struct rate_ctr_group *ctrg;
+};
+
+enum bsc_cfg_ctr {
+	BCFG_CTR_SCCP_CONN,
+	BCFG_CTR_SCCP_CALLS,
+	BCFG_CTR_NET_RECONN,
+	BCFG_CTR_DROPPED_SCCP,
+	BCFG_CTR_DROPPED_CALLS,
+	BCFG_CTR_REJECTED_CR,
+	BCFG_CTR_REJECTED_MSG,
+	BCFG_CTR_ILL_PACKET,
+	BCFG_CTR_CON_TYPE_LU,
+	BCFG_CTR_CON_CMSERV_RQ,
+	BCFG_CTR_CON_PAG_RESP,
+	BCFG_CTR_CON_SSA,
+	BCFG_CTR_CON_OTHER,
+};
+
+/**
+ * One BSC entry in the config
+ */
+struct bsc_config {
+	struct llist_head entry;
+
+	char *token;
+	int nr;
+
+	char *description;
+
+	/* imsi white and blacklist */
+	char *acc_lst_name;
+
+	int forbid_paging;
+
+	/* audio handling */
+	int max_endpoints;
+
+	/* backpointer */
+	struct bsc_nat *nat;
+
+	struct bsc_config_stats stats;
+
+	struct llist_head lac_list;
+};
+
+struct bsc_lac_entry {
+	struct llist_head entry;
+	uint16_t lac;
+};
+
+/**
+ * BSCs point of view of endpoints
+ */
+struct bsc_endpoint {
+	/* the operation that is carried out */
+	int transaction_state;
+	/* the pending transaction id */
+	char *transaction_id;
+	/* the bsc we are talking to */
+	struct bsc_connection *bsc;
+};
+
+/**
+ * Statistic for the nat.
+ */
+struct bsc_nat_statistics {
+	struct {
+		struct counter *conn;
+		struct counter *calls;
+	} sccp;
+
+	struct {
+		struct counter *reconn;
+                struct counter *auth_fail;
+	} bsc;
+
+	struct {
+		struct counter *reconn;
+	} msc;
+
+	struct {
+		struct counter *reconn;
+	} ussd;
+};
+
+enum bsc_nat_acc_ctr {
+	ACC_LIST_BSC_FILTER,
+	ACC_LIST_NAT_FILTER,
+};
+
+struct bsc_nat_acc_lst {
+	struct llist_head list;
+
+	/* counter */
+	struct rate_ctr_group *stats;
+
+	/* the name of the list */
+	const char *name;
+	struct llist_head fltr_list;
+};
+
+struct bsc_nat_acc_lst_entry {
+	struct llist_head list;
+
+	/* the filter */
+	char *imsi_allow;
+	regex_t imsi_allow_re;
+	char *imsi_deny;
+	regex_t imsi_deny_re;
+};
+
+/**
+ * the structure of the "nat" network
+ */
+struct bsc_nat {
+	/* active SCCP connections that need patching */
+	struct llist_head sccp_connections;
+
+	/* active BSC connections that need patching */
+	struct llist_head bsc_connections;
+
+	/* access lists */
+	struct llist_head access_lists;
+
+	/* known BSC's */
+	struct llist_head bsc_configs;
+	int num_bsc;
+	int bsc_ip_dscp;
+
+	/* MGCP config */
+	struct mgcp_config *mgcp_cfg;
+	uint8_t mgcp_msg[4096];
+	int mgcp_length;
+
+	/* msc things */
+	char *msc_ip;
+	int msc_port;
+	struct bsc_msc_connection *msc_con;
+	char *token;
+
+	/* timeouts */
+	int auth_timeout;
+	int ping_timeout;
+	int pong_timeout;
+
+	struct bsc_endpoint *bsc_endpoints;
+
+	/* filter */
+	char *acc_lst_name;
+
+	/* number rewriting */
+	char *num_rewr_name;
+	struct msg_entries *num_rewr;
+
+	/* USSD messages  we want to match */
+	char *ussd_lst_name;
+	char *ussd_query;
+	char *ussd_token;
+	char *ussd_local;
+	struct bsc_fd ussd_listen;
+	struct bsc_nat_ussd_con *ussd_con;
+
+	/* statistics */
+	struct bsc_nat_statistics stats;
+};
+
+/* create and init the structures */
+struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token);
+struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num);
+void bsc_config_free(struct bsc_config *);
+void bsc_config_add_lac(struct bsc_config *cfg, int lac);
+void bsc_config_del_lac(struct bsc_config *cfg, int lac);
+int bsc_config_handles_lac(struct bsc_config *cfg, int lac);
+
+struct bsc_nat *bsc_nat_alloc(void);
+struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat);
+void bsc_nat_set_msc_ip(struct bsc_nat *bsc, const char *ip);
+
+void sccp_connection_destroy(struct sccp_connections *);
+void bsc_close_connection(struct bsc_connection *);
+
+const char *bsc_con_type_to_string(int type);
+
+/**
+ * parse the given message into the above structure
+ */
+struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg);
+
+/**
+ * filter based on IP Access header in both directions
+ */
+int bsc_nat_filter_ipa(int direction, struct msgb *msg, struct bsc_nat_parsed *parsed);
+int bsc_nat_vty_init(struct bsc_nat *nat);
+struct bsc_connection *bsc_nat_find_bsc(struct bsc_nat *nat, struct msgb *msg, int *_lac);
+
+/**
+ * Content filtering.
+ */
+int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg,
+			   struct bsc_nat_parsed *, int *con_type, char **imsi);
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+		      struct sccp_connections *con, struct bsc_nat_parsed *parsed);
+
+/**
+ * SCCP patching and handling
+ */
+struct sccp_connections *create_sccp_src_ref(struct bsc_connection *bsc, struct bsc_nat_parsed *parsed);
+int update_sccp_src_ref(struct sccp_connections *sccp, struct bsc_nat_parsed *parsed);
+void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed);
+struct sccp_connections *patch_sccp_src_ref_to_bsc(struct msgb *, struct bsc_nat_parsed *, struct bsc_nat *);
+struct sccp_connections *patch_sccp_src_ref_to_msc(struct msgb *, struct bsc_nat_parsed *, struct bsc_connection *);
+struct sccp_connections *bsc_nat_find_con_by_bsc(struct bsc_nat *, struct sccp_source_reference *);
+
+/**
+ * MGCP/Audio handling
+ */
+int bsc_mgcp_nr_multiplexes(int max_endpoints);
+int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length);
+int bsc_mgcp_assign_patch(struct sccp_connections *, struct msgb *msg);
+void bsc_mgcp_init(struct sccp_connections *);
+void bsc_mgcp_dlcx(struct sccp_connections *);
+void bsc_mgcp_free_endpoints(struct bsc_nat *nat);
+int bsc_mgcp_nat_init(struct bsc_nat *nat);
+
+struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *, int endpoint_number);
+struct msgb *bsc_mgcp_rewrite(char *input, int length, int endp, const char *ip, int port);
+void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg);
+
+void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc);
+int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60]);
+uint32_t bsc_mgcp_extract_ci(const char *resp);
+
+
+int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int id);
+int bsc_do_write(struct write_queue *queue, struct msgb *msg, int id);
+int bsc_write_msg(struct write_queue *queue, struct msgb *msg);
+int bsc_write_cb(struct bsc_fd *bfd, struct msgb *msg);
+
+/* IMSI allow/deny handling */
+void bsc_parse_reg(void *ctx, regex_t *reg, char **imsi, int argc, const char **argv);
+struct bsc_nat_acc_lst *bsc_nat_acc_lst_find(struct bsc_nat *nat, const char *name);
+struct bsc_nat_acc_lst *bsc_nat_acc_lst_get(struct bsc_nat *nat, const char *name);
+void bsc_nat_acc_lst_delete(struct bsc_nat_acc_lst *lst);
+
+struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *);
+int bsc_nat_lst_check_allow(struct bsc_nat_acc_lst *lst, const char *imsi);
+
+int bsc_nat_msc_is_connected(struct bsc_nat *nat);
+
+int bsc_conn_type_to_ctr(struct sccp_connections *conn);
+
+struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed, struct msgb *msg, uint32_t *len);
+
+/** USSD filtering */
+int bsc_ussd_init(struct bsc_nat *nat);
+int bsc_check_ussd(struct sccp_connections *con, struct bsc_nat_parsed *parsed, struct msgb *msg);
+int bsc_close_ussd_connections(struct bsc_nat *nat);
+
+struct msgb *bsc_nat_rewrite_setup(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *, const char *imsi);
+
+#endif
diff --git a/include/openbsc/bsc_nat_sccp.h b/include/openbsc/bsc_nat_sccp.h
new file mode 100644
index 0000000..0ade668
--- /dev/null
+++ b/include/openbsc/bsc_nat_sccp.h
@@ -0,0 +1,95 @@
+/* NAT utilities using SCCP types */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BSC_NAT_SCCP_H
+#define BSC_NAT_SCCP_H
+
+#include <sys/types.h>
+#include <osmocom/sccp/sccp_types.h>
+
+/*
+ * For the NAT we will need to analyze and later patch
+ * the received message. This would require us to parse
+ * the IPA and SCCP header twice. Instead of doing this
+ * we will have one analyze structure and have the patching
+ * and filter operate on the same structure.
+ */
+struct bsc_nat_parsed {
+	/* ip access prototype */
+	int ipa_proto;
+
+	/* source local reference */
+	struct sccp_source_reference *src_local_ref;
+
+	/* destination local reference */
+	struct sccp_source_reference *dest_local_ref;
+
+	/* called ssn number */
+	int called_ssn;
+
+	/* calling ssn number */
+	int calling_ssn;
+
+	/* sccp message type */
+	int sccp_type;
+
+	/* bssap type, e.g. 0 for BSS Management */
+	int bssap;
+
+	/* the gsm0808 message type */
+	int gsm_type;
+};
+
+/*
+ * Per SCCP source local reference patch table. It needs to
+ * be updated on new SCCP connections, connection confirm and reject,
+ * and on the loss of the BSC connection.
+ */
+struct sccp_connections {
+	struct llist_head list_entry;
+
+	struct bsc_connection *bsc;
+	struct bsc_msc_connection *msc_con;
+
+	struct sccp_source_reference real_ref;
+	struct sccp_source_reference patched_ref;
+	struct sccp_source_reference remote_ref;
+	int has_remote_ref;
+
+	/* status */
+	int con_type;
+	int con_local;
+	int imsi_checked;
+	char *imsi;
+
+	/*
+	 * audio handling. Remember if we have ever send a CRCX,
+	 * remember the endpoint used by the MSC and BSC.
+	 */
+	int msc_endp;
+	int bsc_endp;
+
+	/* timeout handling */
+	struct timespec creation_time;
+};
+
+
+#endif
diff --git a/include/openbsc/bsc_rll.h b/include/openbsc/bsc_rll.h
new file mode 100644
index 0000000..b2898d1
--- /dev/null
+++ b/include/openbsc/bsc_rll.h
@@ -0,0 +1,19 @@
+#ifndef _BSC_RLL_H
+#define _BSC_RLL_H
+
+#include <openbsc/gsm_data.h>
+
+enum bsc_rllr_ind {
+	BSC_RLLR_IND_EST_CONF,
+	BSC_RLLR_IND_REL_IND,
+	BSC_RLLR_IND_ERR_IND,
+	BSC_RLLR_IND_TIMEOUT,
+};
+
+int rll_establish(struct gsm_lchan *lchan, u_int8_t link_id,
+		  void (*cb)(struct gsm_lchan *, u_int8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data);
+void rll_indication(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t type);
+
+#endif /* _BSC_RLL_H */
diff --git a/include/openbsc/chan_alloc.h b/include/openbsc/chan_alloc.h
new file mode 100644
index 0000000..5eda312
--- /dev/null
+++ b/include/openbsc/chan_alloc.h
@@ -0,0 +1,65 @@
+/* Management functions to allocate/release struct gsm_lchan */
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef _CHAN_ALLOC_H
+#define _CHAN_ALLOC_H
+
+#include "gsm_data.h"
+
+struct gsm_subscriber_connection;
+
+/* Special allocator for C0 of BTS */
+struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts,
+				   enum gsm_phys_chan_config pchan);
+
+/* Regular physical channel allocator */
+struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts,
+				enum gsm_phys_chan_config pchan);
+
+/* Regular physical channel (TS) */
+void ts_free(struct gsm_bts_trx_ts *ts);
+
+/* Find an allocated channel for a specified subscriber */
+struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr);
+
+/* Allocate a logical channel (SDCCH, TCH, ...) */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, int allow_bigger);
+
+/* Free a logical channel (SDCCH, TCH, ...) */
+void lchan_free(struct gsm_lchan *lchan);
+void lchan_reset(struct gsm_lchan *lchan);
+
+/* Release the given lchan */
+int lchan_release(struct gsm_lchan *lchan, int sach_deact, int reason);
+
+struct load_counter {
+	unsigned int total;
+	unsigned int used;
+};
+
+struct pchan_load {
+	struct load_counter pchan[GSM_PCHAN_UNKNOWN];
+};
+
+void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts);
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net);
+
+int trx_is_usable(struct gsm_bts_trx *trx);
+
+#endif /* _CHAN_ALLOC_H */
diff --git a/include/openbsc/crc24.h b/include/openbsc/crc24.h
new file mode 100644
index 0000000..358fcb5
--- /dev/null
+++ b/include/openbsc/crc24.h
@@ -0,0 +1,8 @@
+#ifndef _CRC24_H
+#define _CRC24_H
+
+#define INIT_CRC24	0xffffff
+
+u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len);
+
+#endif
diff --git a/include/openbsc/db.h b/include/openbsc/db.h
new file mode 100644
index 0000000..a939b0d
--- /dev/null
+++ b/include/openbsc/db.h
@@ -0,0 +1,83 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _DB_H
+#define _DB_H
+
+#include <sys/types.h>
+
+struct gsm_equipment;
+struct gsm_network;
+struct gsm_auth_info;
+struct gsm_auth_tuple;
+struct gsm_sms;
+struct gsm_subscriber;
+
+enum gsm_subscriber_field;
+
+/* one time initialisation */
+int db_init(const char *name);
+int db_prepare();
+int db_fini();
+
+/* subscriber management */
+struct gsm_subscriber *db_create_subscriber(struct gsm_network *net,
+					    char *imsi);
+struct gsm_subscriber *db_get_subscriber(struct gsm_network *net,
+					 enum gsm_subscriber_field field,
+					 const char *subscr);
+int db_sync_subscriber(struct gsm_subscriber *subscriber);
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber);
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber);
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, u_int32_t* token);
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char *imei);
+int db_sync_equipment(struct gsm_equipment *equip);
+int db_subscriber_update(struct gsm_subscriber *subscriber);
+
+/* auth info */
+int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                               struct gsm_subscriber *subscr);
+int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                                struct gsm_subscriber *subscr);
+int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                    struct gsm_subscriber *subscr);
+int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                     struct gsm_subscriber *subscr);
+
+/* SMS store-and-forward */
+int db_sms_store(struct gsm_sms *sms);
+struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id);
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id);
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, unsigned long long min_subscr_id, unsigned int failed);
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr);
+int db_sms_mark_sent(struct gsm_sms *sms);
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms);
+
+/* APDU blob storage */
+int db_apdu_blob_store(struct gsm_subscriber *subscr, 
+			u_int8_t apdu_id_flags, u_int8_t len,
+			u_int8_t *apdu);
+
+/* Statistics counter storage */
+struct counter;
+int db_store_counter(struct counter *ctr);
+struct rate_ctr_group;
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg);
+
+#endif /* _DB_H */
diff --git a/include/openbsc/debug.h b/include/openbsc/debug.h
new file mode 100644
index 0000000..eb290e4
--- /dev/null
+++ b/include/openbsc/debug.h
@@ -0,0 +1,70 @@
+#ifndef _DEBUG_H
+#define _DEBUG_H
+
+#include <stdio.h>
+#include <osmocore/linuxlist.h>
+
+#define DEBUG
+#include <osmocore/logging.h>
+
+/* Debug Areas of the code */
+enum {
+	DRLL,
+	DCC,
+	DMM,
+	DRR,
+	DRSL,
+	DNM,
+	DMNCC,
+	DSMS,
+	DPAG,
+	DMEAS,
+	DMI,
+	DMIB,
+	DMUX,
+	DINP,
+	DSCCP,
+	DMSC,
+	DMGCP,
+	DHO,
+	DDB,
+	DREF,
+	DGPRS,
+	DNS,
+	DBSSGP,
+	DLLC,
+	DSNDCP,
+	DNAT,
+	Debug_LastEntry,
+};
+
+/* context */
+#define BSC_CTX_LCHAN	0
+#define BSC_CTX_SUBSCR	1
+#define BSC_CTX_BTS	2
+#define BSC_CTX_SCCP	3
+#define BSC_CTX_NSVC	4
+#define BSC_CTX_BVC	5
+
+/* target */
+
+enum {
+	//DEBUG_FILTER_ALL = 1 << 0,
+	LOG_FILTER_IMSI = 1 << 1,
+	LOG_FILTER_NSVC = 1 << 2,
+	LOG_FILTER_BVC  = 1 << 3,
+};
+
+/* we don't need a header dependency for this... */
+struct gprs_nsvc;
+struct bssgp_bvc_ctx;
+
+void log_set_imsi_filter(struct log_target *target, const char *imsi);
+void log_set_nsvc_filter(struct log_target *target,
+			 struct gprs_nsvc *nsvc);
+void log_set_bvc_filter(struct log_target *target,
+			struct bssgp_bvc_ctx *bctx);
+
+extern const struct log_info log_info;
+
+#endif /* _DEBUG_H */
diff --git a/include/openbsc/e1_input.h b/include/openbsc/e1_input.h
new file mode 100644
index 0000000..3c8af38
--- /dev/null
+++ b/include/openbsc/e1_input.h
@@ -0,0 +1,187 @@
+#ifndef _E1_INPUT_H
+#define _E1_INPUT_H
+
+#include <stdlib.h>
+#include <netinet/in.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/select.h>
+#include <openbsc/subchan_demux.h>
+
+#define NUM_E1_TS   32
+
+enum e1inp_sign_type {
+	E1INP_SIGN_NONE,
+	E1INP_SIGN_OML,
+	E1INP_SIGN_RSL,
+};
+const char *e1inp_signtype_name(enum e1inp_sign_type tp);
+
+struct e1inp_ts;
+
+struct e1inp_sign_link {
+	/* list of signalling links */
+	struct llist_head list;
+
+	/* to which timeslot do we belong? */
+	struct e1inp_ts *ts;
+
+	enum e1inp_sign_type type;
+
+	/* trx for msg->trx of received msgs */	
+	struct gsm_bts_trx *trx;
+
+	/* msgb queue of to-be-transmitted msgs */
+	struct llist_head tx_list;
+
+	/* SAPI and TEI on the E1 TS */
+	u_int8_t sapi;
+	u_int8_t tei;
+
+	union {
+		struct {
+			u_int8_t channel;
+		} misdn;
+	} driver;
+};
+
+enum e1inp_ts_type {
+	E1INP_TS_TYPE_NONE,
+	E1INP_TS_TYPE_SIGN,
+	E1INP_TS_TYPE_TRAU,
+};
+const char *e1inp_tstype_name(enum e1inp_ts_type tp);
+
+/* A timeslot in the E1 interface */
+struct e1inp_ts {
+	enum e1inp_ts_type type;
+	int num;
+
+	/* to which line do we belong ? */
+	struct e1inp_line *line;
+
+	union {
+		struct {
+			/* list of all signalling links on this TS */
+			struct llist_head sign_links;
+			/* delay for the queue */
+			int delay;
+			/* timer when to dequeue next frame */
+			struct timer_list tx_timer;
+		} sign;
+		struct {
+			/* subchannel demuxer for frames from E1 */
+			struct subch_demux demux;
+			/* subchannel muxer for frames to E1 */
+			struct subch_mux mux;
+		} trau;
+	};
+	union {
+		struct {
+			/* mISDN driver has one fd for each ts */
+			struct bsc_fd fd;
+		} misdn;
+		struct {
+			/* ip.access driver has one fd for each ts */
+			struct bsc_fd fd;
+		} ipaccess;
+		struct {
+			/* DAHDI driver has one fd for each ts */
+			struct bsc_fd fd;
+			struct lapd_instance *lapd;
+		} dahdi;
+	} driver;
+};
+
+struct e1inp_driver {
+	struct llist_head list;
+	const char *name;
+	int (*want_write)(struct e1inp_ts *ts);
+	int (*line_update)(struct e1inp_line *line);
+	int default_delay;
+};	
+
+struct e1inp_line {
+	struct llist_head list;
+	unsigned int num;
+	const char *name;
+
+	/* array of timestlots */
+	struct e1inp_ts ts[NUM_E1_TS];
+
+	struct e1inp_driver *driver;
+	void *driver_data;
+};
+
+/* register a driver with the E1 core */
+int e1inp_driver_register(struct e1inp_driver *drv);
+
+/* fine a previously registered driver */
+struct e1inp_driver *e1inp_driver_find(const char *name);
+
+/* register a line with the E1 core */
+int e1inp_line_register(struct e1inp_line *line);
+
+/* get a line by its ID */
+struct e1inp_line *e1inp_line_get(u_int8_t e1_nr);
+
+/* create a line in the E1 input core */
+struct e1inp_line *e1inp_line_create(u_int8_t e1_nr, const char *driver_name);
+
+/* find a sign_link for given TEI and SAPI in a TS */
+struct e1inp_sign_link *
+e1inp_lookup_sign_link(struct e1inp_ts *ts, u_int8_t tei,
+			u_int8_t sapi);
+
+/* create a new signalling link in a E1 timeslot */
+struct e1inp_sign_link *
+e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type,
+			struct gsm_bts_trx *trx, u_int8_t tei,
+			u_int8_t sapi);
+
+/* configure and initialize one e1inp_ts */
+int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line,
+		    enum e1inp_ts_type type);
+
+/* Call from the Stack: configuration of this TS has changed */
+int e1inp_update_ts(struct e1inp_ts *ts);
+
+/* Receive a packet from the E1 driver */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+		u_int8_t tei, u_int8_t sapi);
+
+/* called by driver if it wants to transmit on a given TS */
+struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts,
+			 struct e1inp_sign_link **sign_link);
+
+/* called by driver in case some kind of link state event */
+int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi);
+
+/* Write LAPD frames to the fd. */
+void e1_set_pcap_fd(int fd);
+
+/* called by TRAU muxer to obtain the destination mux entity */
+struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr);
+
+void e1inp_sign_link_destroy(struct e1inp_sign_link *link);
+int e1inp_line_update(struct e1inp_line *line);
+
+/* e1_config.c */
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts);
+int e1_reconfig_trx(struct gsm_bts_trx *trx);
+int e1_reconfig_bts(struct gsm_bts *bts);
+
+int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin);
+int ipaccess_setup(struct gsm_network *gsmnet);
+
+extern struct llist_head e1inp_driver_list;
+extern struct llist_head e1inp_line_list;
+
+int e1inp_vty_init(void);
+void e1inp_init(void);
+
+int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml);
+
+#endif /* _E1_INPUT_H */
diff --git a/include/openbsc/gb_proxy.h b/include/openbsc/gb_proxy.h
new file mode 100644
index 0000000..18ded22
--- /dev/null
+++ b/include/openbsc/gb_proxy.h
@@ -0,0 +1,39 @@
+#ifndef _GB_PROXY_H
+#define _GB_PROXY_H
+
+#include <sys/types.h>
+
+#include <osmocore/msgb.h>
+
+#include <openbsc/gprs_ns.h>
+#include <osmocom/vty/command.h>
+
+struct gbproxy_config {
+	/* parsed from config file */
+	u_int16_t nsip_sgsn_nsei;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+};
+
+extern struct gbproxy_config gbcfg;
+extern struct cmd_element show_gbproxy_cmd;
+
+/* gb_proxy_vty .c */
+
+int gbproxy_vty_init(void);
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg);
+
+
+/* gb_proxy.c */
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+		  void *handler_data, void *signal_data);
+
+/* Reset all persistent NS-VC's */
+int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi);
+
+#endif
diff --git a/include/openbsc/gprs_bssgp.h b/include/openbsc/gprs_bssgp.h
new file mode 100644
index 0000000..e432cf7
--- /dev/null
+++ b/include/openbsc/gprs_bssgp.h
@@ -0,0 +1,232 @@
+#ifndef _GPRS_BSSGP_H
+#define _GPRS_BSSGP_H
+
+#include <stdint.h>
+
+/* Section 5.4.1 */
+#define BVCI_SIGNALLING	0x0000
+#define BVCI_PTM	0x0001
+
+/* Section 11.3.26 / Table 11.27 */
+enum bssgp_pdu_type {
+	/* PDUs between RL and BSSGP SAPs */
+	BSSGP_PDUT_DL_UNITDATA		= 0x00,
+	BSSGP_PDUT_UL_UNITDATA		= 0x01,
+	BSSGP_PDUT_RA_CAPABILITY	= 0x02,
+	BSSGP_PDUT_PTM_UNITDATA		= 0x03,
+	/* PDUs between GMM SAPs */
+	BSSGP_PDUT_PAGING_PS		= 0x06,
+	BSSGP_PDUT_PAGING_CS		= 0x07,
+	BSSGP_PDUT_RA_CAPA_UDPATE	= 0x08,
+	BSSGP_PDUT_RA_CAPA_UPDATE_ACK	= 0x09,
+	BSSGP_PDUT_RADIO_STATUS		= 0x0a,
+	BSSGP_PDUT_SUSPEND		= 0x0b,
+	BSSGP_PDUT_SUSPEND_ACK		= 0x0c,
+	BSSGP_PDUT_SUSPEND_NACK		= 0x0d,
+	BSSGP_PDUT_RESUME		= 0x0e,
+	BSSGP_PDUT_RESUME_ACK		= 0x0f,
+	BSSGP_PDUT_RESUME_NACK		= 0x10,
+	/* PDus between NM SAPs */
+	BSSGP_PDUT_BVC_BLOCK		= 0x20,
+	BSSGP_PDUT_BVC_BLOCK_ACK	= 0x21,
+	BSSGP_PDUT_BVC_RESET		= 0x22,
+	BSSGP_PDUT_BVC_RESET_ACK	= 0x23,
+	BSSGP_PDUT_BVC_UNBLOCK		= 0x24,
+	BSSGP_PDUT_BVC_UNBLOCK_ACK	= 0x25,
+	BSSGP_PDUT_FLOW_CONTROL_BVC	= 0x26,
+	BSSGP_PDUT_FLOW_CONTROL_BVC_ACK	= 0x27,
+	BSSGP_PDUT_FLOW_CONTROL_MS	= 0x28,
+	BSSGP_PDUT_FLOW_CONTROL_MS_ACK	= 0x29,
+	BSSGP_PDUT_FLUSH_LL		= 0x2a,
+	BSSGP_PDUT_FLUSH_LL_ACK		= 0x2b,
+	BSSGP_PDUT_LLC_DISCARD		= 0x2c,
+	BSSGP_PDUT_SGSN_INVOKE_TRACE	= 0x40,
+	BSSGP_PDUT_STATUS		= 0x41,
+	/* PDUs between PFM SAP's */
+	BSSGP_PDUT_DOWNLOAD_BSS_PFC	= 0x50,
+	BSSGP_PDUT_CREATE_BSS_PFC	= 0x51,
+	BSSGP_PDUT_CREATE_BSS_PFC_ACK	= 0x52,
+	BSSGP_PDUT_CREATE_BSS_PFC_NACK	= 0x53,
+	BSSGP_PDUT_MODIFY_BSS_PFC	= 0x54,
+	BSSGP_PDUT_MODIFY_BSS_PFC_ACK	= 0x55,
+	BSSGP_PDUT_DELETE_BSS_PFC	= 0x56,
+	BSSGP_PDUT_DELETE_BSS_PFC_ACK	= 0x57,
+};
+
+/* Section 10.2.1 and 10.2.2 */
+struct bssgp_ud_hdr {
+	uint8_t pdu_type;
+	uint32_t tlli;
+	uint8_t qos_profile[3];
+	uint8_t data[0];	/* TLV's */
+} __attribute__((packed));
+
+struct bssgp_normal_hdr {
+	uint8_t pdu_type;
+	uint8_t data[0];	/* TLV's */
+};
+
+enum bssgp_iei_type {
+	BSSGP_IE_ALIGNMENT		= 0x00,
+	BSSGP_IE_BMAX_DEFAULT_MS	= 0x01,
+	BSSGP_IE_BSS_AREA_ID		= 0x02,
+	BSSGP_IE_BUCKET_LEAK_RATE	= 0x03,
+	BSSGP_IE_BVCI			= 0x04,
+	BSSGP_IE_BVC_BUCKET_SIZE	= 0x05,
+	BSSGP_IE_BVC_MEASUREMENT	= 0x06,
+	BSSGP_IE_CAUSE			= 0x07,
+	BSSGP_IE_CELL_ID		= 0x08,
+	BSSGP_IE_CHAN_NEEDED		= 0x09,
+	BSSGP_IE_DRX_PARAMS		= 0x0a,
+	BSSGP_IE_EMLPP_PRIO		= 0x0b,
+	BSSGP_IE_FLUSH_ACTION		= 0x0c,
+	BSSGP_IE_IMSI			= 0x0d,
+	BSSGP_IE_LLC_PDU		= 0x0e,
+	BSSGP_IE_LLC_FRAMES_DISCARDED	= 0x0f,
+	BSSGP_IE_LOCATION_AREA		= 0x10,
+	BSSGP_IE_MOBILE_ID		= 0x11,
+	BSSGP_IE_MS_BUCKET_SIZE		= 0x12,
+	BSSGP_IE_MS_RADIO_ACCESS_CAP	= 0x13,
+	BSSGP_IE_OMC_ID			= 0x14,
+	BSSGP_IE_PDU_IN_ERROR		= 0x15,
+	BSSGP_IE_PDU_LIFETIME		= 0x16,
+	BSSGP_IE_PRIORITY		= 0x17,
+	BSSGP_IE_QOS_PROFILE		= 0x18,
+	BSSGP_IE_RADIO_CAUSE		= 0x19,
+	BSSGP_IE_RA_CAP_UPD_CAUSE	= 0x1a,
+	BSSGP_IE_ROUTEING_AREA		= 0x1b,
+	BSSGP_IE_R_DEFAULT_MS		= 0x1c,
+	BSSGP_IE_SUSPEND_REF_NR		= 0x1d,
+	BSSGP_IE_TAG			= 0x1e,
+	BSSGP_IE_TLLI			= 0x1f,
+	BSSGP_IE_TMSI			= 0x20,
+	BSSGP_IE_TRACE_REFERENC		= 0x21,
+	BSSGP_IE_TRACE_TYPE		= 0x22,
+	BSSGP_IE_TRANSACTION_ID		= 0x23,
+	BSSGP_IE_TRIGGER_ID		= 0x24,
+	BSSGP_IE_NUM_OCT_AFF		= 0x25,
+	BSSGP_IE_LSA_ID_LIST		= 0x26,
+	BSSGP_IE_LSA_INFORMATION	= 0x27,
+	BSSGP_IE_PACKET_FLOW_ID		= 0x28,
+	BSSGP_IE_PACKET_FLOW_TIMER	= 0x29,
+	BSSGP_IE_AGG_BSS_QOS_PROFILE	= 0x3a,
+	BSSGP_IE_FEATURE_BITMAP		= 0x3b,
+	BSSGP_IE_BUCKET_FULL_RATIO	= 0x3c,
+	BSSGP_IE_SERVICE_UTRAN_CCO	= 0x3d,
+};
+
+/* Section 11.3.8 / Table 11.10: Cause coding */
+enum gprs_bssgp_cause {
+	BSSGP_CAUSE_PROC_OVERLOAD	= 0x00,
+	BSSGP_CAUSE_EQUIP_FAIL		= 0x01,
+	BSSGP_CAUSE_TRASIT_NET_FAIL	= 0x02,
+	BSSGP_CAUSE_CAPA_GREATER_0KPBS	= 0x03,
+	BSSGP_CAUSE_UNKNOWN_MS		= 0x04,
+	BSSGP_CAUSE_UNKNOWN_BVCI	= 0x05,
+	BSSGP_CAUSE_CELL_TRAF_CONG	= 0x06,
+	BSSGP_CAUSE_SGSN_CONG		= 0x07,
+	BSSGP_CAUSE_OML_INTERV		= 0x08,
+	BSSGP_CAUSE_BVCI_BLOCKED	= 0x09,
+	BSSGP_CAUSE_PFC_CREATE_FAIL	= 0x0a,
+	BSSGP_CAUSE_SEM_INCORR_PDU	= 0x20,
+	BSSGP_CAUSE_INV_MAND_INF	= 0x21,
+	BSSGP_CAUSE_MISSING_MAND_IE	= 0x22,
+	BSSGP_CAUSE_MISSING_COND_IE	= 0x23,
+	BSSGP_CAUSE_UNEXP_COND_IE	= 0x24,
+	BSSGP_CAUSE_COND_IE_ERR		= 0x25,
+	BSSGP_CAUSE_PDU_INCOMP_STATE	= 0x26,
+	BSSGP_CAUSE_PROTO_ERR_UNSPEC	= 0x27,
+	BSSGP_CAUSE_PDU_INCOMP_FEAT	= 0x28,
+};
+
+/* Our implementation */
+
+/* gprs_bssgp_util.c */
+extern struct gprs_ns_inst *bssgp_nsi;
+struct msgb *bssgp_msgb_alloc(void);
+const char *bssgp_cause_str(enum gprs_bssgp_cause cause);
+/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
+int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
+			 uint16_t bvci, uint16_t ns_bvci);
+/* Chapter 10.4.14: Status */
+int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg);
+
+/* gprs_bssgp.c */
+
+#define BVC_S_BLOCKED	0x0001
+
+/* The per-BTS context that we keep on the SGSN side of the BSSGP link */
+struct bssgp_bvc_ctx {
+	struct llist_head list;
+
+	/* parsed RA ID and Cell ID of the remote BTS */
+	struct gprs_ra_id ra_id;
+	uint16_t cell_id;
+
+	/* NSEI and BVCI of underlying Gb link.  Together they
+	 * uniquely identify a link to a BTS (5.4.4) */
+	uint16_t bvci;
+	uint16_t nsei;
+
+	uint32_t state;
+
+	struct rate_ctr_group *ctrg;
+
+	/* we might want to add this as a shortcut later, avoiding the NSVC
+	 * lookup for every packet, similar to a routing cache */
+	//struct gprs_nsvc *nsvc;
+};
+extern struct llist_head bssgp_bvc_ctxts;
+/* Find a BTS Context based on parsed RA ID and Cell ID */
+struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid);
+/* Find a BTS context based on BVCI+NSEI tuple */
+struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei);
+
+#include <osmocore/tlv.h>
+
+/* BSSGP-UL-UNITDATA.ind */
+int gprs_bssgp_rcvmsg(struct msgb *msg);
+
+/* BSSGP-DL-UNITDATA.req */
+struct sgsn_mm_ctx;
+int gprs_bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx);
+
+uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf);
+
+/* Wrapper around TLV parser to parse BSSGP IEs */
+static inline int bssgp_tlv_parse(struct tlv_parsed *tp, uint8_t *buf, int len)
+{
+	return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0);
+}
+
+enum bssgp_paging_mode {
+	BSSGP_PAGING_PS,
+	BSSGP_PAGING_CS,
+};
+
+enum bssgp_paging_scope {
+	BSSGP_PAGING_BSS_AREA,		/* all cells in BSS */
+	BSSGP_PAGING_LOCATION_AREA,	/* all cells in LA */
+	BSSGP_PAGING_ROUTEING_AREA,	/* all cells in RA */
+	BSSGP_PAGING_BVCI,		/* one cell */
+};
+
+struct bssgp_paging_info {
+	enum bssgp_paging_mode mode;
+	enum bssgp_paging_scope scope;
+	struct gprs_ra_id raid;
+	uint16_t bvci;
+	const char *imsi;
+	uint32_t *ptmsi;
+	uint16_t drx_params;
+	uint8_t qos[3];
+};
+
+/* Send a single GMM-PAGING.req to a given NSEI/NS-BVCI */
+int gprs_bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
+			 struct bssgp_paging_info *pinfo);
+
+/* gprs_bssgp_vty.c */
+int gprs_bssgp_vty_init(void);
+
+#endif /* _GPRS_BSSGP_H */
diff --git a/include/openbsc/gprs_gmm.h b/include/openbsc/gprs_gmm.h
new file mode 100644
index 0000000..bd129ae
--- /dev/null
+++ b/include/openbsc/gprs_gmm.h
@@ -0,0 +1,19 @@
+#ifndef _GPRS_GMM_H
+#define _GPRS_GMM_H
+
+#include <osmocore/msgb.h>
+#include <openbsc/gprs_sgsn.h>
+
+int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause);
+int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid,
+			     uint8_t cause, uint8_t pco_len, uint8_t *pco_v);
+int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp);
+int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp);
+
+int gsm0408_gprs_rcvmsg(struct msgb *msg, struct gprs_llc_llme *llme);
+
+int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli);
+int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli,
+		       uint8_t suspend_ref);
+
+#endif /* _GPRS_GMM_H */
diff --git a/include/openbsc/gprs_llc.h b/include/openbsc/gprs_llc.h
new file mode 100644
index 0000000..02945e1
--- /dev/null
+++ b/include/openbsc/gprs_llc.h
@@ -0,0 +1,162 @@
+#ifndef _GPRS_LLC_H
+#define _GPRS_LLC_H
+
+#include <stdint.h>
+#include <openbsc/gprs_sgsn.h>
+
+/* Section 4.7 LLC Layer Structure */
+enum gprs_llc_sapi {
+	GPRS_SAPI_GMM		= 1,
+	GPRS_SAPI_TOM2		= 2,
+	GPRS_SAPI_SNDCP3	= 3,
+	GPRS_SAPI_SNDCP5	= 5,
+	GPRS_SAPI_SMS		= 7,
+	GPRS_SAPI_TOM8		= 8,
+	GPRS_SAPI_SNDCP9	= 9,
+	GPRS_SAPI_SNDCP11	= 11,
+};
+
+/* Section 6.4 Commands and Responses */
+enum gprs_llc_u_cmd {
+	GPRS_LLC_U_DM_RESP		= 0x01,
+	GPRS_LLC_U_DISC_CMD		= 0x04,
+	GPRS_LLC_U_UA_RESP		= 0x06,
+	GPRS_LLC_U_SABM_CMD		= 0x07,
+	GPRS_LLC_U_FRMR_RESP		= 0x08,
+	GPRS_LLC_U_XID			= 0x0b,
+	GPRS_LLC_U_NULL_CMD		= 0x00,
+};
+
+/* TS 04.64 Section 7.1.2 Table 7: LLC layer primitives (GMM/SNDCP/SMS/TOM) */
+/* TS 04.65 Section 5.1.2 Table 2: Service primitives used by SNDCP */
+enum gprs_llc_primitive {
+	/* GMM <-> LLME */
+	LLGMM_ASSIGN_REQ,	/* GMM tells us new TLLI: TLLI old, TLLI new, Kc, CiphAlg */
+	LLGMM_RESET_REQ,	/* GMM tells us to perform XID negotiation: TLLI */
+	LLGMM_RESET_CNF,	/* LLC informs GMM that XID has completed: TLLI */
+	LLGMM_SUSPEND_REQ,	/* GMM tells us MS has suspended: TLLI, Page */
+	LLGMM_RESUME_REQ,	/* GMM tells us MS has resumed: TLLI */
+	LLGMM_PAGE_IND,		/* LLC asks GMM to page MS: TLLI */
+	LLGMM_IOV_REQ,		/* GMM tells us to perform XID: TLLI */
+	LLGMM_STATUS_IND,	/* LLC informs GMM about error: TLLI, Cause */
+	/* LLE <-> (GMM/SNDCP/SMS/TOM) */
+	LL_RESET_IND,		/* TLLI */
+	LL_ESTABLISH_REQ,	/* TLLI, XID Req */
+	LL_ESTABLISH_IND,	/* TLLI, XID Req, N201-I, N201-U */
+	LL_ESTABLISH_RESP,	/* TLLI, XID Negotiated */
+	LL_ESTABLISH_CONF,	/* TLLI, XID Neg, N201-i, N201-U */
+	LL_RELEASE_REQ,		/* TLLI, Local */
+	LL_RELEASE_IND,		/* TLLI, Cause */
+	LL_RELEASE_CONF,	/* TLLI */
+	LL_XID_REQ,		/* TLLI, XID Requested */
+	LL_XID_IND,		/* TLLI, XID Req, N201-I, N201-U */
+	LL_XID_RESP,		/* TLLI, XID Negotiated */
+	LL_XID_CONF,		/* TLLI, XID Neg, N201-I, N201-U */
+	LL_DATA_REQ,		/* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */
+	LL_DATA_IND,		/* TLLI, SN-PDU */
+	LL_DATA_CONF,		/* TLLI, Ref */
+	LL_UNITDATA_REQ,	/* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */
+	LL_UNITDATA_IND,	/* TLLI, SN-PDU */
+	LL_STATUS_IND,		/* TLLI, Cause */
+};
+
+/* Section 4.5.2 Logical Link States + Annex C.2 */
+enum gprs_llc_lle_state {
+	GPRS_LLES_UNASSIGNED	= 1,	/* No TLLI yet */
+	GPRS_LLES_ASSIGNED_ADM	= 2,	/* TLLI assigned */
+	GPRS_LLES_LOCAL_EST	= 3,	/* Local Establishment */
+	GPRS_LLES_REMOTE_EST	= 4,	/* Remote Establishment */
+	GPRS_LLES_ABM		= 5,
+	GPRS_LLES_LOCAL_REL	= 6,	/* Local Release */
+	GPRS_LLES_TIMER_REC 	= 7,	/* Timer Recovery */
+};
+
+enum gprs_llc_llme_state {
+	GPRS_LLMS_UNASSIGNED	= 1,	/* No TLLI yet */
+	GPRS_LLMS_ASSIGNED	= 2,	/* TLLI assigned */
+};
+
+/* Section 8.9.9 LLC layer parameter default values */
+struct gprs_llc_params {
+	uint16_t iov_i_exp;
+	uint16_t t200_201;
+	uint16_t n200;
+	uint16_t n201_u;
+	uint16_t n201_i;
+	uint16_t mD;
+	uint16_t mU;
+	uint16_t kD;
+	uint16_t kU;
+};
+
+/* Section 4.7.1: Logical Link Entity: One per DLCI (TLLI + SAPI) */
+struct gprs_llc_lle {
+	struct llist_head list;
+
+	uint32_t sapi;
+
+	struct gprs_llc_llme *llme;
+
+	enum gprs_llc_lle_state state;
+
+	struct timer_list t200;
+	struct timer_list t201;	/* wait for acknowledgement */
+
+	uint16_t v_sent;
+	uint16_t v_ack;
+	uint16_t v_recv;
+
+	uint16_t vu_send;
+	uint16_t vu_recv;
+
+	/* Overflow Counter for ABM */
+	uint32_t oc_i_send;
+	uint32_t oc_i_recv;
+
+	/* Overflow Counter for unconfirmed transfer */
+	uint32_t oc_ui_send;
+	uint32_t oc_ui_recv;
+
+	unsigned int retrans_ctr;
+
+	struct gprs_llc_params params;
+};
+
+#define NUM_SAPIS	16
+
+struct gprs_llc_llme {
+	struct llist_head list;
+
+	enum gprs_llc_llme_state state;
+
+	uint32_t tlli;
+	uint32_t old_tlli;
+
+	/* Crypto parameters */
+	enum gprs_ciph_algo algo;
+	uint8_t kc[8];
+
+	/* over which BSSGP BTS ctx do we need to transmit */
+	uint16_t bvci;
+	uint16_t nsei;
+	struct gprs_llc_lle lle[NUM_SAPIS];
+};
+
+extern struct llist_head gprs_llc_llmes;
+
+/* BSSGP-UL-UNITDATA.ind */
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv);
+
+/* LL-UNITDATA.req */
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command,
+		   void *mmctx);
+
+/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */
+int gprs_llgmm_assign(struct gprs_llc_llme *llme,
+		      uint32_t old_tlli, uint32_t new_tlli,
+		      enum gprs_ciph_algo alg, const uint8_t *kc);
+
+int gprs_llc_init(const char *cipher_plugin_path);
+int gprs_llc_vty_init(void);
+
+#endif
diff --git a/include/openbsc/gprs_ns.h b/include/openbsc/gprs_ns.h
new file mode 100644
index 0000000..953c364
--- /dev/null
+++ b/include/openbsc/gprs_ns.h
@@ -0,0 +1,232 @@
+#ifndef _GPRS_NS_H
+#define _GPRS_NS_H
+
+#include <stdint.h>
+
+/* GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * 3GPP TS 48.016 version 6.5.0 Release 6 / ETSI TS 148 016 V6.5.0 (2005-11) */
+
+struct gprs_ns_hdr {
+	uint8_t pdu_type;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* TS 08.16, Section 10.3.7, Table 14 */
+enum ns_pdu_type {
+	NS_PDUT_UNITDATA	= 0x00,
+	NS_PDUT_RESET		= 0x02,
+	NS_PDUT_RESET_ACK	= 0x03,
+	NS_PDUT_BLOCK		= 0x04,
+	NS_PDUT_BLOCK_ACK	= 0x05,
+	NS_PDUT_UNBLOCK		= 0x06,
+	NS_PDUT_UNBLOCK_ACK	= 0x07,
+	NS_PDUT_STATUS		= 0x08,
+	NS_PDUT_ALIVE		= 0x0a,
+	NS_PDUT_ALIVE_ACK	= 0x0b,
+	/* TS 48.016 Section 10.3.7, Table 10.3.7.1 */
+	SNS_PDUT_ACK		= 0x0c,
+	SNS_PDUT_ADD		= 0x0d,
+	SNS_PDUT_CHANGE_WEIGHT	= 0x0e,
+	SNS_PDUT_CONFIG		= 0x0f,
+	SNS_PDUT_CONFIG_ACK	= 0x10,
+	SNS_PDUT_DELETE		= 0x11,
+	SNS_PDUT_SIZE		= 0x12,
+	SNS_PDUT_SIZE_ACK	= 0x13,
+};
+
+/* TS 08.16, Section 10.3, Table 12 */
+enum ns_ctrl_ie {
+	NS_IE_CAUSE		= 0x00,
+	NS_IE_VCI		= 0x01,
+	NS_IE_PDU		= 0x02,
+	NS_IE_BVCI		= 0x03,
+	NS_IE_NSEI		= 0x04,
+	/* TS 48.016 Section 10.3, Table 10.3.1 */
+	NS_IE_IPv4_LIST		= 0x05,
+	NS_IE_IPv6_LIST		= 0x06,
+	NS_IE_MAX_NR_NSVC	= 0x07,
+	NS_IE_IPv4_EP_NR	= 0x08,
+	NS_IE_IPv6_EP_NR	= 0x09,
+	NS_IE_RESET_FLAG	= 0x0a,
+	NS_IE_IP_ADDR		= 0x0b,
+};
+
+/* TS 08.16, Section 10.3.2, Table 13 */
+enum ns_cause {
+	NS_CAUSE_TRANSIT_FAIL		= 0x00,
+	NS_CAUSE_OM_INTERVENTION	= 0x01,
+	NS_CAUSE_EQUIP_FAIL		= 0x02,
+	NS_CAUSE_NSVC_BLOCKED		= 0x03,
+	NS_CAUSE_NSVC_UNKNOWN		= 0x04,
+	NS_CAUSE_BVCI_UNKNOWN		= 0x05,
+	NS_CAUSE_SEM_INCORR_PDU		= 0x08,
+	NS_CAUSE_PDU_INCOMP_PSTATE	= 0x0a,
+	NS_CAUSE_PROTO_ERR_UNSPEC	= 0x0b,
+	NS_CAUSE_INVAL_ESSENT_IE	= 0x0c,
+	NS_CAUSE_MISSING_ESSENT_IE	= 0x0d,
+	/* TS 48.016 Section 10.3.2, Table 10.3.2.1 */
+	NS_CAUSE_INVAL_NR_IPv4_EP	= 0x0e,
+	NS_CAUSE_INVAL_NR_IPv6_EP	= 0x0f,
+	NS_CAUSE_INVAL_NR_NS_VC		= 0x10,
+	NS_CAUSE_INVAL_WEIGH		= 0x11,
+	NS_CAUSE_UNKN_IP_EP		= 0x12,
+	NS_CAUSE_UNKN_IP_ADDR		= 0x13,
+	NS_CAUSE_UNKN_IP_TEST_FAILED	= 0x14,
+};
+
+/* Our Implementation */
+#include <netinet/in.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/msgb.h>
+#include <osmocore/timer.h>
+#include <osmocore/select.h>
+
+#define NS_TIMERS_COUNT 7
+#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries)"
+#define NS_TIMERS_HELP	\
+	"(un)blocking Timer (Tns-block) timeout\n"		\
+	"(un)blocking Timer (Tns-block) number of retries\n"	\
+	"Reset Timer (Tns-reset) timeout\n"			\
+	"Reset Timer (Tns-reset) number of retries\n"		\
+	"Test Timer (Tns-test) timeout\n"			\
+
+enum ns_timeout {
+	NS_TOUT_TNS_BLOCK,
+	NS_TOUT_TNS_BLOCK_RETRIES,
+	NS_TOUT_TNS_RESET,
+	NS_TOUT_TNS_RESET_RETRIES,
+	NS_TOUT_TNS_TEST,
+	NS_TOUT_TNS_ALIVE,
+	NS_TOUT_TNS_ALIVE_RETRIES,
+};
+
+#define NSE_S_BLOCKED	0x0001
+#define NSE_S_ALIVE	0x0002
+
+enum gprs_ns_ll {
+	GPRS_NS_LL_UDP,
+	GPRS_NS_LL_E1,
+	GPRS_NS_LL_FR_GRE,
+};
+
+enum gprs_ns_evt {
+	GPRS_NS_EVT_UNIT_DATA,
+};
+
+struct gprs_nsvc;
+typedef int gprs_ns_cb_t(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+			 struct msgb *msg, uint16_t bvci);
+
+/* An instance of the NS protocol stack */
+struct gprs_ns_inst {
+	/* callback to the user for incoming UNIT DATA IND */
+	gprs_ns_cb_t *cb;
+
+	/* linked lists of all NSVC in this instance */
+	struct llist_head gprs_nsvcs;
+
+	/* a NSVC object that's needed to deal with packets for unknown NSVC */
+	struct gprs_nsvc *unknown_nsvc;
+
+	uint16_t timeout[NS_TIMERS_COUNT];
+
+	/* NS-over-IP specific bits */
+	struct {
+		struct bsc_fd fd;
+		uint32_t local_ip;
+		uint16_t local_port;
+	} nsip;
+	/* NS-over-FR-over-GRE-over-IP specific bits */
+	struct {
+		struct bsc_fd fd;
+		uint32_t local_ip;
+		int enabled:1;
+	} frgre;
+};
+
+enum nsvc_timer_mode {
+	/* standard timers */
+	NSVC_TIMER_TNS_TEST,
+	NSVC_TIMER_TNS_ALIVE,
+	NSVC_TIMER_TNS_RESET,
+	_NSVC_TIMER_NR,
+};
+
+struct gprs_nsvc {
+	struct llist_head list;
+	struct gprs_ns_inst *nsi;
+
+	uint16_t nsei;		/* end-to-end significance */
+	uint16_t nsvci;	/* uniquely identifies NS-VC at SGSN */
+
+	uint32_t state;
+	uint32_t remote_state;
+
+	struct timer_list timer;
+	enum nsvc_timer_mode timer_mode;
+	int alive_retries;
+
+	unsigned int remote_end_is_sgsn:1;
+	unsigned int persistent:1;
+
+	struct rate_ctr_group *ctrg;
+
+	/* which link-layer are we based on? */
+	enum gprs_ns_ll ll;
+
+	union {
+		struct {
+			struct sockaddr_in bts_addr;
+		} ip;
+		struct {
+			struct sockaddr_in bts_addr;
+		} frgre;
+	};
+};
+
+/* Create a new NS protocol instance */
+struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb);
+
+/* Destroy a NS protocol instance */
+void gprs_ns_destroy(struct gprs_ns_inst *nsi);
+
+/* Listen for incoming GPRS packets via NS/UDP */
+int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi);
+
+struct sockaddr_in;
+
+/* main function for higher layers (BSSGP) to send NS messages */
+int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg);
+
+int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause);
+int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause);
+int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc);
+
+/* Listen for incoming GPRS packets via NS/FR/GRE */
+int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi);
+
+/* Establish a connection (from the BSS) to the SGSN */
+struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci);
+
+struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci);
+void nsvc_delete(struct gprs_nsvc *nsvc);
+struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei);
+struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi, uint16_t nsvci);
+
+/* Initiate a RESET procedure (including timer start, ...)*/
+void gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause);
+
+/* Add NS-specific VTY stuff */
+int gprs_ns_vty_init(struct gprs_ns_inst *nsi);
+
+#define NS_ALLOC_SIZE	2048
+#define NS_ALLOC_HEADROOM 20
+static inline struct msgb *gprs_ns_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, "GPRS/NS");
+}
+
+#endif
diff --git a/include/openbsc/gprs_ns_frgre.h b/include/openbsc/gprs_ns_frgre.h
new file mode 100644
index 0000000..abcd43f
--- /dev/null
+++ b/include/openbsc/gprs_ns_frgre.h
@@ -0,0 +1,6 @@
+#ifndef _GPRS_NS_FRGRE_H
+#define _GPRS_NS_FRGRE_H
+
+int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
+
+#endif
diff --git a/include/openbsc/gprs_sgsn.h b/include/openbsc/gprs_sgsn.h
new file mode 100644
index 0000000..b470c53
--- /dev/null
+++ b/include/openbsc/gprs_sgsn.h
@@ -0,0 +1,220 @@
+#ifndef _GPRS_SGSN_H
+#define _GPRS_SGSN_H
+
+#include <stdint.h>
+#include <netinet/in.h>
+
+#include <osmocore/gsm48.h>
+
+#include <osmocom/crypt/gprs_cipher.h>
+
+#define GSM_IMSI_LENGTH 17
+#define GSM_IMEI_LENGTH 17
+#define GSM_EXTENSION_LENGTH 15
+
+struct gprs_llc_lle;
+
+/* TS 04.08 4.1.3.3 GMM mobility management states on the network side */
+enum gprs_mm_state {
+	GMM_DEREGISTERED,		/* 4.1.3.3.1.1 */
+	GMM_COMMON_PROC_INIT,		/* 4.1.3.3.1.2 */
+	GMM_REGISTERED_NORMAL,		/* 4.1.3.3.2.1 */
+	GMM_REGISTERED_SUSPENDED,	/* 4.1.3.3.2.2 */
+	GMM_DEREGISTERED_INIT,		/* 4.1.3.3.1.4 */
+};
+
+enum gprs_mm_ctr {
+	GMM_CTR_PKTS_SIG_IN,
+	GMM_CTR_PKTS_SIG_OUT,
+	GMM_CTR_PKTS_UDATA_IN,
+	GMM_CTR_PKTS_UDATA_OUT,
+	GMM_CTR_BYTES_UDATA_IN,
+	GMM_CTR_BYTES_UDATA_OUT,
+	GMM_CTR_PDP_CTX_ACT,
+	GMM_CTR_SUSPEND,
+	GMM_CTR_PAGING_PS,
+	GMM_CTR_PAGING_CS,
+	GMM_CTR_RA_UPDATE,
+};
+
+enum gprs_pdp_ctx {
+	PDP_CTR_PKTS_UDATA_IN,
+	PDP_CTR_PKTS_UDATA_OUT,
+	PDP_CTR_BYTES_UDATA_IN,
+	PDP_CTR_BYTES_UDATA_OUT,
+};
+
+enum gprs_t3350_mode {
+	GMM_T3350_MODE_ATT,
+	GMM_T3350_MODE_RAU,
+	GMM_T3350_MODE_PTMSI_REALL,
+};
+
+#define MS_RADIO_ACCESS_CAPA
+
+/* According to TS 03.60, Table 5: SGSN MM and PDP Contexts */
+/* Extended by 3GPP TS 23.060, Table 6: SGSN MM and PDP Contexts */
+struct sgsn_mm_ctx {
+	struct llist_head	list;
+
+	char 			imsi[GSM_IMSI_LENGTH];
+	enum gprs_mm_state	mm_state;
+	uint32_t 		p_tmsi;
+	uint32_t 		p_tmsi_old;	/* old P-TMSI before new is confirmed */
+	uint32_t 		p_tmsi_sig;
+	char 			imei[GSM_IMEI_LENGTH];
+	/* Opt: Software Version Numbber / TS 23.195 */
+	char 			msisdn[GSM_EXTENSION_LENGTH];
+	struct gprs_ra_id	ra;
+	uint16_t		cell_id;
+	uint32_t		cell_id_age;
+	uint16_t		sac;	/* Iu: Service Area Code */
+	uint32_t		sac_age;/* Iu: Service Area Code age */
+	/* VLR number */
+	uint32_t		new_sgsn_addr;
+	/* Authentication Triplets */
+	/* Kc */
+	/* Iu: CK, IK, KSI */
+	/* CKSN */
+	enum gprs_ciph_algo	ciph_algo;
+	struct {
+		uint8_t	buf[14];	/* 10.5.5.12a */
+		uint8_t	len;
+	} ms_radio_access_capa;
+	struct {
+		uint8_t	buf[4];		/* 10.5.5.12 */
+		uint8_t	len;
+	} ms_network_capa;
+	uint16_t		drx_parms;
+	int			mnrg;	/* MS reported to HLR? */
+	int			ngaf;	/* MS reported to MSC/VLR? */
+	int			ppf;	/* paging for GPRS + non-GPRS? */
+	/* SMS Parameters */
+	int			recovery;
+	uint8_t			radio_prio_sms;
+
+	struct llist_head	pdp_list;
+
+	/* Additional bits not present in the GSM TS */
+	struct gprs_llc_llme	*llme;
+	uint32_t		tlli;
+	uint32_t		tlli_new;
+	uint16_t		nsei;
+	uint16_t		bvci;
+	struct rate_ctr_group	*ctrg;
+	struct timer_list	timer;
+	unsigned int		T;		/* Txxxx number */
+	unsigned int		num_T_exp;	/* number of consecutive T expirations */
+
+	enum gprs_t3350_mode	t3350_mode;
+	uint8_t			t3370_id_type;
+};
+
+/* look-up a SGSN MM context based on TLLI + RAI */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t tmsi);
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi);
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+					const struct gprs_ra_id *raid);
+void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm);
+
+
+enum pdp_ctx_state {
+	PDP_STATE_NONE,
+	PDP_STATE_CR_REQ,
+	PDP_STATE_CR_CONF,
+
+	/* 04.08 / Figure 6.2 / 6.1.2.2 */
+	PDP_STATE_INACT_PEND,
+	PDP_STATE_INACTIVE = PDP_STATE_NONE,
+};
+
+enum pdp_type {
+	PDP_TYPE_NONE,
+	PDP_TYPE_ETSI_PPP,
+	PDP_TYPE_IANA_IPv4,
+	PDP_TYPE_IANA_IPv6,
+};
+
+struct sgsn_pdp_ctx {
+	struct llist_head	list;	/* list_head for mmctx->pdp_list */
+	struct llist_head	g_list;	/* list_head for global list */
+	struct sgsn_mm_ctx	*mm;	/* back pointer to MM CTX */
+	struct sgsn_ggsn_ctx	*ggsn;	/* which GGSN serves this PDP */
+	struct rate_ctr_group	*ctrg;
+
+	//unsigned int		id;
+	struct pdp_t		*lib;	/* pointer to libgtp PDP ctx */
+	enum pdp_ctx_state	state;
+	enum pdp_type		type;
+	uint32_t		address;
+	char 			*apn_subscribed;
+	//char 			*apn_used;
+	uint16_t		nsapi;	/* SNDCP */
+	uint16_t		sapi;	/* LLC */
+	uint8_t			ti;	/* transaction identifier */
+	int			vplmn_allowed;
+	uint32_t		qos_profile_subscr;
+	//uint32_t		qos_profile_req;
+	//uint32_t		qos_profile_neg;
+	uint8_t			radio_prio;
+	uint32_t		tx_npdu_nr;
+	uint32_t		rx_npdu_nr;
+	uint32_t		tx_gtp_snd;
+	uint32_t		rx_gtp_snu;
+	//uint32_t		charging_id;
+	int			reordering_reqd;
+
+	struct timer_list	timer;
+	unsigned int		T;		/* Txxxx number */
+	unsigned int		num_T_exp;	/* number of consecutive T expirations */
+};
+
+
+/* look up PDP context by MM context and NSAPI */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+					   uint8_t nsapi);
+/* look up PDP context by MM context and transaction ID */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm,
+					 uint8_t tid);
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+					uint8_t nsapi);
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp);
+
+
+struct sgsn_ggsn_ctx {
+	struct llist_head list;
+	uint32_t id;
+	unsigned int gtp_version;
+	struct in_addr remote_addr;
+	int remote_restart_ctr;
+	struct gsn_t *gsn;
+};
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id);
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id);
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr);
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id);
+
+struct apn_ctx {
+	struct llist_head list;
+	struct sgsn_ggsn_ctx *ggsn;
+	char *name;
+	char *description;
+};
+
+extern struct llist_head sgsn_mm_ctxts;
+extern struct llist_head sgsn_ggsn_ctxts;
+extern struct llist_head sgsn_apn_ctxts;
+extern struct llist_head sgsn_pdp_ctxts;
+
+uint32_t sgsn_alloc_ptmsi(void);
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * ottherwise lost state (recovery procedure) */
+int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn);
+
+#endif /* _GPRS_SGSN_H */
diff --git a/include/openbsc/gsm_04_08.h b/include/openbsc/gsm_04_08.h
new file mode 100644
index 0000000..1c879ed
--- /dev/null
+++ b/include/openbsc/gsm_04_08.h
@@ -0,0 +1,70 @@
+#ifndef _GSM_04_08_H
+#define _GSM_04_08_H
+
+#include <openbsc/meas_rep.h>
+
+#include <osmocore/protocol/gsm_04_08.h>
+#include <osmocore/gsm48.h>
+
+struct msgb;
+struct gsm_bts;
+struct gsm_subscriber;
+struct gsm_network;
+struct gsm_trans;
+struct gsm_subscriber_connection;
+
+#define GSM48_ALLOC_SIZE	2048
+#define GSM48_ALLOC_HEADROOM	256
+
+static inline struct msgb *gsm48_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM48_ALLOC_SIZE, GSM48_ALLOC_HEADROOM,
+				   "GSM 04.08");
+}
+
+/* config options controlling the behaviour of the lower leves */
+void gsm0408_allow_everyone(int allow);
+void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
+void gsm0408_clear_all_trans(struct gsm_network *net, int protocol);
+int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+int gsm0408_rcvmsg(struct msgb *msg, u_int8_t link_id);
+int gsm0408_new_conn(struct gsm_subscriber_connection *conn);
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *bts, u_int8_t ra);
+enum gsm_chreq_reason_t get_reason_by_chreq(u_int8_t ra, int neci);
+void gsm_net_update_ctype(struct gsm_network *net);
+
+int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn);
+int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, u_int8_t *rand, int key_seq);
+int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn);
+int gsm48_send_rr_release(struct gsm_lchan *lchan);
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv);
+int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, u_int8_t apdu_id,
+			   u_int8_t apdu_len, const u_int8_t *apdu);
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, u_int8_t power_class);
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+		      u_int8_t power_command, u_int8_t ho_ref);
+
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg);
+
+/* convert a ASCII phone number to call-control BCD */
+int encode_bcd_number(u_int8_t *bcd_lv, u_int8_t max_len,
+		      int h_len, const char *input);
+int decode_bcd_number(char *output, int output_len, const u_int8_t *bcd_lv,
+		      int h_len);
+
+int send_siemens_mrpci(struct gsm_lchan *lchan, u_int8_t *classmark2_lv);
+int gsm48_extract_mi(uint8_t *classmark2, int length, char *mi_string, uint8_t *mi_type);
+int gsm48_paging_extract_mi(struct gsm48_pag_resp *pag, int length, char *mi_string, u_int8_t *mi_type);
+int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn, struct msgb *msg, struct gsm_subscriber *subscr);
+
+int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode);
+int gsm48_rx_rr_modif_ack(struct msgb *msg);
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg);
+
+struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value);
+struct msgb *gsm48_create_loc_upd_rej(uint8_t cause);
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+			   const struct gsm_lchan *lchan);
+
+#endif
diff --git a/include/openbsc/gsm_04_08_gprs.h b/include/openbsc/gsm_04_08_gprs.h
new file mode 100644
index 0000000..8de6362
--- /dev/null
+++ b/include/openbsc/gsm_04_08_gprs.h
@@ -0,0 +1,383 @@
+#ifndef _GSM48_GPRS_H
+#define _GSM48_GPRS_H
+
+#include <stdint.h>
+#include <osmocore/protocol/gsm_04_08.h>
+
+/* Table 10.4 / 10.4a, GPRS Mobility Management (GMM) */
+#define GSM48_MT_GMM_ATTACH_REQ		0x01
+#define GSM48_MT_GMM_ATTACH_ACK		0x02
+#define GSM48_MT_GMM_ATTACH_COMPL	0x03
+#define GSM48_MT_GMM_ATTACH_REJ		0x04
+#define GSM48_MT_GMM_DETACH_REQ		0x05
+#define GSM48_MT_GMM_DETACH_ACK		0x06
+
+#define GSM48_MT_GMM_RA_UPD_REQ		0x08
+#define GSM48_MT_GMM_RA_UPD_ACK		0x09
+#define GSM48_MT_GMM_RA_UPD_COMPL	0x0a
+#define GSM48_MT_GMM_RA_UPD_REJ		0x0b
+
+#define GSM48_MT_GMM_PTMSI_REALL_CMD	0x10
+#define GSM48_MT_GMM_PTMSI_REALL_COMPL	0x11
+#define GSM48_MT_GMM_AUTH_CIPH_REQ	0x12
+#define GSM48_MT_GMM_AUTH_CIPH_RESP	0x13
+#define GSM48_MT_GMM_AUTH_CIPH_REJ	0x14
+#define GSM48_MT_GMM_ID_REQ		0x15
+#define GSM48_MT_GMM_ID_RESP		0x16
+#define GSM48_MT_GMM_STATUS		0x20
+#define GSM48_MT_GMM_INFO		0x21
+
+/* Table 10.4a, GPRS Session Management (GSM) */
+#define GSM48_MT_GSM_ACT_PDP_REQ	0x41
+#define GSM48_MT_GSM_ACT_PDP_ACK	0x42
+#define GSM48_MT_GSM_ACT_PDP_REJ	0x43
+#define GSM48_MT_GSM_REQ_PDP_ACT	0x44
+#define GSM48_MT_GSM_REQ_PDP_ACT_REJ	0x45
+#define GSM48_MT_GSM_DEACT_PDP_REQ	0x46
+#define GSM48_MT_GSM_DEACT_PDP_ACK	0x47
+#define GSM48_MT_GSM_ACT_AA_PDP_REQ	0x50
+#define GSM48_MT_GSM_ACT_AA_PDP_ACK	0x51
+#define GSM48_MT_GSM_ACT_AA_PDP_REJ	0x52
+#define GSM48_MT_GSM_DEACT_AA_PDP_REQ	0x53
+#define GSM48_MT_GSM_DEACT_AA_PDP_ACK	0x54
+#define GSM48_MT_GSM_STATUS		0x55
+
+/* Chapter 10.5.5.2 / Table 10.5.135 */
+#define GPRS_ATT_T_ATTACH		1
+#define GPRS_ATT_T_ATT_WHILE_IMSI	2
+#define GPRS_ATT_T_COMBINED		3
+
+/* Chapter 10.5.5.5 / Table 10.5.138 */
+#define GPRS_DET_T_MO_GPRS		1
+#define GPRS_DET_T_MO_IMSI		2
+#define GPRS_DET_T_MO_COMBINED		3
+/* Network to MS direction */
+#define GPRS_DET_T_MT_REATT_REQ		1
+#define GPRS_DET_T_MT_REATT_NOTREQ	2
+#define GPRS_DET_T_MT_IMSI		3
+
+/* Chapter 10.5.5.18 / Table 105.150 */
+#define GPRS_UPD_T_RA			0
+#define GPRS_UPD_T_RA_LA		1
+#define GPRS_UPD_T_RA_LA_IMSI_ATT	2
+#define GPRS_UPD_T_PERIODIC		3
+
+enum gsm48_gprs_ie_mm {
+	GSM48_IE_GMM_CIPH_CKSN		= 0x08, /* 10.5.1.2 */
+	GSM48_IE_GMM_TIMER_READY	= 0x17,	/* 10.5.7.3 */
+	GSM48_IE_GMM_ALLOC_PTMSI	= 0x18,	/* 10.5.1.4 */
+	GSM48_IE_GMM_PTMSI_SIG		= 0x19,	/* 10.5.5.8 */
+	GSM48_IE_GMM_AUTH_RAND		= 0x21,	/* 10.5.3.1 */
+	GSM48_IE_GMM_AUTH_SRES		= 0x22,	/* 10.5.3.2 */
+	GSM48_IE_GMM_IMEISV		= 0x23,	/* 10.5.1.4 */
+	GSM48_IE_GMM_DRX_PARAM		= 0x27,	/* 10.5.5.6 */
+	GSM48_IE_GMM_MS_NET_CAPA	= 0x31,	/* 10.5.5.12 */
+	GSM48_IE_GMM_PDP_CTX_STATUS	= 0x32,	/* 10.5.7.1 */
+	GSM48_IE_GMM_PS_LCS_CAPA	= 0x33,	/* 10.5.5.22 */
+	GSM48_IE_GMM_GMM_MBMS_CTX_ST	= 0x35,	/* 10.5.7.6 */
+};
+
+enum gsm48_gprs_ie_sm {
+	GSM48_IE_GSM_APN		= 0x28,	/* 10.5.6.1 */
+	GSM48_IE_GSM_PROTO_CONF_OPT	= 0x27,	/* 10.5.6.3 */
+	GSM48_IE_GSM_PDP_ADDR		= 0x2b, /* 10.5.6.4 */
+	GSM48_IE_GSM_AA_TMR		= 0x29,	/* 10.5.7.3 */
+	GSM48_IE_GSM_NAME_FULL		= 0x43, /* 10.5.3.5a */
+	GSM48_IE_GSM_NAME_SHORT		= 0x45, /* 10.5.3.5a */
+	GSM48_IE_GSM_TIMEZONE		= 0x46, /* 10.5.3.8 */
+	GSM48_IE_GSM_UTC_AND_TZ		= 0x47, /* 10.5.3.9 */
+	GSM48_IE_GSM_LSA_ID		= 0x48, /* 10.5.3.11 */
+
+	/* Fake IEs that are not present on the Layer3 air interface,
+	 * but which we use to simplify internal APIs */
+	OSMO_IE_GSM_REQ_QOS		= 0xfd,
+	OSMO_IE_GSM_REQ_PDP_ADDR	= 0xfe,
+};
+
+/* Chapter 9.4.15 / Table 9.4.15 */
+struct gsm48_ra_upd_ack {
+	uint8_t force_stby:4,	/* 10.5.5.7 */
+		 upd_result:4;	/* 10.5.5.17 */
+	uint8_t ra_upd_timer;	/* 10.5.7.3 */
+	struct gsm48_ra_id ra_id; /* 10.5.5.15 */
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.7.3 */
+enum gsm48_gprs_tmr_unit {
+	GPRS_TMR_2SECONDS	= 0 << 5,
+	GPRS_TMR_MINUTE		= 1 << 5,
+	GPRS_TMR_6MINUTE	= 2 << 5,
+	GPRS_TMR_DEACTIVATED	= 3 << 5,
+};
+
+/* Chapter 9.4.2 / Table 9.4.2 */
+struct gsm48_attach_ack {
+	uint8_t att_result:4,	/* 10.5.5.7 */
+		 force_stby:4;	/* 10.5.5.1 */
+	uint8_t ra_upd_timer;	/* 10.5.7.3 */
+	uint8_t radio_prio;	/* 10.5.7.2 */
+	struct gsm48_ra_id ra_id; /* 10.5.5.15 */
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.4.9 / Table 9.4.9 */
+struct gsm48_auth_ciph_req {
+	uint8_t ciph_alg:4,	/* 10.5.5.3 */
+		imeisv_req:4;	/* 10.5.5.10 */
+	uint8_t force_stby:4,	/* 10.5.5.7 */
+		ac_ref_nr:4;	/* 10.5.5.19 */
+	uint8_t data[0];
+} __attribute__((packed));
+/* optional: TV RAND, TV CKSN */
+
+struct gsm48_auth_ciph_resp {
+	uint8_t ac_ref_nr:4,
+		spare:4;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.5.1 / Table 9.5.1 */
+struct gsm48_act_pdp_ctx_req {
+	uint8_t req_nsapi;
+	uint8_t req_llc_sapi;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.5.14 / Table 10.5.147 */
+enum gsm48_gmm_cause {
+	GMM_CAUSE_IMSI_UNKNOWN		= 0x02,
+	GMM_CAUSE_ILLEGAL_MS		= 0x03,
+	GMM_CAUSE_ILLEGAL_ME		= 0x06,
+	GMM_CAUSE_GPRS_NOTALLOWED	= 0x07,
+	GMM_CAUSE_GPRS_OTHER_NOTALLOWED	= 0x08,
+	GMM_CAUSE_MS_ID_NOT_DERIVED	= 0x09,
+	GMM_CAUSE_IMPL_DETACHED		= 0x0a,
+	GMM_CAUSE_PLMN_NOTALLOWED	= 0x0b,
+	GMM_CAUSE_LA_NOTALLOWED		= 0x0c,
+	GMM_CAUSE_ROAMING_NOTALLOWED	= 0x0d,
+	GMM_CAUSE_NO_GPRS_PLMN		= 0x0e,
+	GMM_CAUSE_MSC_TEMP_NOTREACH	= 0x10,
+	GMM_CAUSE_NET_FAIL		= 0x11,
+	GMM_CAUSE_CONGESTION		= 0x16,
+	GMM_CAUSE_SEM_INCORR_MSG	= 0x5f,
+	GMM_CAUSE_INV_MAND_INFO		= 0x60,
+	GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL	= 0x61,
+	GMM_CAUSE_MSGT_INCOMP_P_STATE	= 0x62,
+	GMM_CAUSE_IE_NOTEXIST_NOTIMPL	= 0x63,
+	GMM_CAUSE_COND_IE_ERR		= 0x64,
+	GMM_CAUSE_MSG_INCOMP_P_STATE	= 0x65,
+	GMM_CAUSE_PROTO_ERR_UNSPEC	= 0x6f,
+};
+
+/* Chapter 10.4.6.6 / Table 10.5.157 */
+enum gsm48_gsm_cause {
+	GSM_CAUSE_INSUFF_RSRC		= 0x1a,
+	GSM_CAUSE_MISSING_APN		= 0x1b,
+	GSM_CAUSE_UNKNOWN_PDP		= 0x1c,
+	GSM_CAUSE_AUTH_FAILED		= 0x1d,
+	GSM_CAUSE_ACT_REJ_GGSN		= 0x1e,
+	GSM_CAUSE_ACT_REJ_UNSPEC	= 0x1f,
+	GSM_CAUSE_SERV_OPT_NOTSUPP	= 0x20,
+	GSM_CAUSE_REQ_SERV_OPT_NOTSUB	= 0x21,
+	GSM_CAUSE_SERV_OPT_TEMP_OOO	= 0x22,
+	GSM_CAUSE_NSAPI_IN_USE		= 0x23,
+	GSM_CAUSE_DEACT_REGULAR		= 0x24,
+	GSM_CAUSE_QOS_NOT_ACCEPTED	= 0x25,
+	GSM_CAUSE_NET_FAIL		= 0x26,
+	GSM_CAUSE_REACT_RQD		= 0x27,
+	GSM_CAUSE_FEATURE_NOTSUPP	= 0x28,
+	GSM_CAUSE_INVALID_TRANS_ID	= 0x51,
+	GSM_CAUSE_SEM_INCORR_MSG	= 0x5f,
+	GSM_CAUSE_INV_MAND_INFO		= 0x60,
+	GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL	= 0x61,
+	GSM_CAUSE_MSGT_INCOMP_P_STATE	= 0x62,
+	GSM_CAUSE_IE_NOTEXIST_NOTIMPL	= 0x63,
+	GSM_CAUSE_COND_IE_ERR		= 0x64,
+	GSM_CAUSE_MSG_INCOMP_P_STATE	= 0x65,
+	GSM_CAUSE_PROTO_ERR_UNSPEC	= 0x6f,
+};
+
+/* Section 6.1.2.2: Session management states on the network side */
+enum gsm48_pdp_state {
+	PDP_S_INACTIVE,
+	PDP_S_ACTIVE_PENDING,
+	PDP_S_ACTIVE,
+	PDP_S_INACTIVE_PENDING,
+	PDP_S_MODIFY_PENDING,
+};
+
+/* Table 10.5.155/3GPP TS 24.008 */
+enum gsm48_pdp_type_org {
+	PDP_TYPE_ORG_ETSI		= 0x00,
+	PDP_TYPE_ORG_IETF		= 0x01,
+};
+enum gsm48_pdp_type_nr {
+	PDP_TYPE_N_ETSI_RESERVED	= 0x00,
+	PDP_TYPE_N_ETSI_PPP		= 0x01,
+	PDP_TYPE_N_IETF_IPv4		= 0x21,
+	PDP_TYPE_N_IETF_IPv6		= 0x57,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_reliab_class {
+	GSM48_QOS_RC_LLC_ACK_RLC_ACK_DATA_PROT	= 2,
+	GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT	= 3,
+	GSM48_QOS_RC_LLC_UN_RLC_UN_PROT_DATA	= 4,
+	GSM48_QOS_RC_LLC_UN_RLC_UN_DATA_UN	= 5,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_preced_class {
+	GSM48_QOS_PC_HIGH	= 1,
+	GSM48_QOS_PC_NORMAL	= 2,
+	GSM48_QOS_PC_LOW	= 3,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_peak_tput {
+	GSM48_QOS_PEAK_TPUT_1000bps	= 1,
+	GSM48_QOS_PEAK_TPUT_2000bps	= 2,
+	GSM48_QOS_PEAK_TPUT_4000bps	= 3,
+	GSM48_QOS_PEAK_TPUT_8000bps	= 4,
+	GSM48_QOS_PEAK_TPUT_16000bps	= 5,
+	GSM48_QOS_PEAK_TPUT_32000bps	= 6,
+	GSM48_QOS_PEAK_TPUT_64000bps	= 7,
+	GSM48_QOS_PEAK_TPUT_128000bps	= 8,
+	GSM48_QOS_PEAK_TPUT_256000bps	= 9,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_mean_tput {
+	GSM48_QOS_MEAN_TPUT_100bph	= 1,
+	GSM48_QOS_MEAN_TPUT_200bph	= 2,
+	GSM48_QOS_MEAN_TPUT_500bph	= 3,
+	GSM48_QOS_MEAN_TPUT_1000bph	= 4,
+	GSM48_QOS_MEAN_TPUT_2000bph	= 5,
+	GSM48_QOS_MEAN_TPUT_5000bph	= 6,
+	GSM48_QOS_MEAN_TPUT_10000bph	= 7,
+	GSM48_QOS_MEAN_TPUT_20000bph	= 8,
+	GSM48_QOS_MEAN_TPUT_50000bph	= 9,
+	GSM48_QOS_MEAN_TPUT_100kbph	= 10,
+	GSM48_QOS_MEAN_TPUT_200kbph	= 11,
+	GSM48_QOS_MEAN_TPUT_500kbph	= 0xc,
+	GSM48_QOS_MEAN_TPUT_1Mbph	= 0xd,
+	GSM48_QOS_MEAN_TPUT_2Mbph	= 0xe,
+	GSM48_QOS_MEAN_TPUT_5Mbph	= 0xf,
+	GSM48_QOS_MEAN_TPUT_10Mbph	= 0x10,
+	GSM48_QOS_MEAN_TPUT_20Mbph	= 0x11,
+	GSM48_QOS_MEAN_TPUT_50Mbph	= 0x12,
+	GSM48_QOS_MEAN_TPUT_BEST_EFFORT	= 0x1f,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_err_sdu {
+	GSM48_QOS_ERRSDU_NODETECT	= 1,
+	GSM48_QOS_ERRSDU_YES		= 2,
+	GSM48_QOS_ERRSDU_NO		= 3,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_deliv_order {
+	GSM48_QOS_DO_ORDERED		= 1,
+	GSM48_QOS_DO_UNORDERED		= 2,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_traf_class {
+	GSM48_QOS_TC_CONVERSATIONAL	= 1,
+	GSM48_QOS_TC_STREAMING		= 2,
+	GSM48_QOS_TC_INTERACTIVE	= 3,
+	GSM48_QOS_TC_BACKGROUND		= 4,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_max_sdu_size {
+	/* values below in 10 octet granularity */
+	GSM48_QOS_MAXSDU_1502		= 0x97,
+	GSM48_QOS_MAXSDU_1510		= 0x98,
+	GSM48_QOS_MAXSDU_1520		= 0x99,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_max_bitrate {
+	GSM48_QOS_MBRATE_1k		= 0x01,
+	GSM48_QOS_MBRATE_63k		= 0x3f,
+	GSM48_QOS_MBRATE_64k		= 0x40,
+	GSM48_QOS_MBRATE_568k		= 0x7f,
+	GSM48_QOS_MBRATE_576k		= 0x80,
+	GSM48_QOS_MBRATE_8640k		= 0xfe,
+	GSM48_QOS_MBRATE_0k		= 0xff,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_resid_ber {
+	GSM48_QOS_RBER_5e_2		= 0x01,
+	GSM48_QOS_RBER_1e_2		= 0x02,
+	GSM48_QOS_RBER_5e_3		= 0x03,
+	GSM48_QOS_RBER_4e_3		= 0x04,
+	GSM48_QOS_RBER_1e_3		= 0x05,
+	GSM48_QOS_RBER_1e_4		= 0x06,
+	GSM48_QOS_RBER_1e_5		= 0x07,
+	GSM48_QOS_RBER_1e_6		= 0x08,
+	GSM48_QOS_RBER_6e_8		= 0x09,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+enum gsm48_qos_sdu_err {
+	GSM48_QOS_SERR_1e_2		= 0x01,
+	GSM48_QOS_SERR_7e_2		= 0x02,
+	GSM48_QOS_SERR_1e_3		= 0x03,
+	GSM48_QOS_SERR_1e_4		= 0x04,
+	GSM48_QOS_SERR_1e_5		= 0x05,
+	GSM48_QOS_SERR_1e_6		= 0x06,
+	GSM48_QOS_SERR_1e_1		= 0x07,
+};
+
+/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
+struct gsm48_qos {
+	/* octet 3 */
+	uint8_t reliab_class:3;
+	uint8_t delay_class:3;
+	uint8_t spare:2;
+	/* octet 4 */
+	uint8_t preced_class:3;
+	uint8_t spare2:1;
+	uint8_t peak_tput:4;
+	/* octet 5 */
+	uint8_t mean_tput:5;
+	uint8_t spare3:3;
+	/* octet 6 */
+	uint8_t deliv_err_sdu:3;
+	uint8_t deliv_order:2;
+	uint8_t traf_class:3;
+	/* octet 7 */
+	uint8_t max_sdu_size;
+	/* octet 8 */
+	uint8_t max_bitrate_up;
+	/* octet 9 */
+	uint8_t max_bitrate_down;
+	/* octet 10 */
+	uint8_t sdu_err_ratio:4;
+	uint8_t resid_ber:4;
+	/* octet 11 */
+	uint8_t handling_prio:2;
+	uint8_t xfer_delay:6;
+	/* octet 12 */
+	uint8_t guar_bitrate_up;
+	/* octet 13 */
+	uint8_t guar_bitrate_down;
+	/* octet 14 */
+	uint8_t src_stats_desc:4;
+	uint8_t sig_ind:1;
+	uint8_t spare5:3;
+	/* octet 15 */
+	uint8_t max_bitrate_down_ext;
+	/* octet 16 */
+	uint8_t guar_bitrate_down_ext;
+};
+
+
+int gprs_tlli_type(uint32_t tlli);
+
+#endif /* _GSM48_GPRS_H */
diff --git a/include/openbsc/gsm_04_11.h b/include/openbsc/gsm_04_11.h
new file mode 100644
index 0000000..5969788
--- /dev/null
+++ b/include/openbsc/gsm_04_11.h
@@ -0,0 +1,40 @@
+#ifndef _GSM_04_11_H
+#define _GSM_04_11_H
+
+#include <osmocore/protocol/gsm_04_11.h>
+
+#define UM_SAPI_SMS 3	/* See GSM 04.05/04.06 */
+
+/* SMS deliver PDU */
+struct sms_deliver {
+	u_int8_t mti:2;		/* message type indicator */
+	u_int8_t mms:1;		/* more messages to send */
+	u_int8_t rp:1;		/* reply path */
+	u_int8_t udhi:1;	/* user data header indicator */
+	u_int8_t sri:1;		/* status report indication */
+	u_int8_t *orig_addr;	/* originating address */
+	u_int8_t pid;		/* protocol identifier */
+	u_int8_t dcs;		/* data coding scheme */
+				/* service centre time stamp */
+	u_int8_t ud_len;	/* user data length */
+	u_int8_t *user_data;	/* user data */
+
+	u_int8_t msg_ref;	/* message reference */
+	u_int8_t *smsc;
+};
+
+struct msgb;
+
+int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+struct gsm_sms *sms_alloc(void);
+void sms_free(struct gsm_sms *sms);
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, int dcs, const char *text);
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans);
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms);
+int gsm411_send_sms(struct gsm_subscriber_connection *conn,
+		    struct gsm_sms *sms);
+void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn);
+#endif
diff --git a/include/openbsc/gsm_04_80.h b/include/openbsc/gsm_04_80.h
new file mode 100644
index 0000000..796a1c1
--- /dev/null
+++ b/include/openbsc/gsm_04_80.h
@@ -0,0 +1,20 @@
+#ifndef _GSM_04_80_H
+#define _GSM_04_80_H
+
+#include <osmocore/msgb.h>
+#include <osmocore/protocol/gsm_04_80.h>
+#include <osmocore/gsm0480.h>
+
+struct gsm_subscriber_connection;
+
+int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
+			       const struct msgb *in_msg, const char* response_text, 
+			       const struct ussd_request *req);
+int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
+			     const struct msgb *msg, 
+			     const struct ussd_request *request);
+
+int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text);
+int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/include/openbsc/gsm_data.h b/include/openbsc/gsm_data.h
new file mode 100644
index 0000000..ae448c4
--- /dev/null
+++ b/include/openbsc/gsm_data.h
@@ -0,0 +1,879 @@
+#ifndef _GSM_DATA_H
+#define _GSM_DATA_H
+
+#include <sys/types.h>
+
+struct osmo_msc_data;
+struct osmo_bsc_sccp_con;
+struct gsm_sms_queue;
+
+enum gsm_phys_chan_config {
+	GSM_PCHAN_NONE,
+	GSM_PCHAN_CCCH,
+	GSM_PCHAN_CCCH_SDCCH4,
+	GSM_PCHAN_TCH_F,
+	GSM_PCHAN_TCH_H,
+	GSM_PCHAN_SDCCH8_SACCH8C,
+	GSM_PCHAN_PDCH,		/* GPRS PDCH */
+	GSM_PCHAN_TCH_F_PDCH,	/* TCH/F if used, PDCH otherwise */
+	GSM_PCHAN_UNKNOWN,
+};
+
+enum gsm_chan_t {
+	GSM_LCHAN_NONE,
+	GSM_LCHAN_SDCCH,
+	GSM_LCHAN_TCH_F,
+	GSM_LCHAN_TCH_H,
+	GSM_LCHAN_UNKNOWN,
+};
+
+/* RRLP mode of operation */
+enum rrlp_mode {
+	RRLP_MODE_NONE,
+	RRLP_MODE_MS_BASED,
+	RRLP_MODE_MS_PREF,
+	RRLP_MODE_ASS_PREF,
+};
+
+/* Channel Request reason */
+enum gsm_chreq_reason_t {
+	GSM_CHREQ_REASON_EMERG,
+	GSM_CHREQ_REASON_PAG,
+	GSM_CHREQ_REASON_CALL,
+	GSM_CHREQ_REASON_LOCATION_UPD,
+	GSM_CHREQ_REASON_OTHER,
+};
+
+#include <osmocore/timer.h>
+#include <openbsc/system_information.h>
+#include <openbsc/rest_octets.h>
+#include <openbsc/mncc.h>
+
+#include <osmocore/tlv.h>
+#include <osmocore/bitvec.h>
+#include <osmocore/statistics.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/utils.h>
+#include <osmocore/rxlev_stat.h>
+
+#include <osmocore/protocol/gsm_08_58.h>
+
+
+#define TRX_NR_TS	8
+#define TS_MAX_LCHAN	8
+
+#define HARDCODED_ARFCN 123
+#define HARDCODED_TSC	7
+#define HARDCODED_BSIC	0x3f	/* NCC = 7 / BCC = 7 */
+
+/* for multi-drop config */
+#define HARDCODED_BTS0_TS	1
+#define HARDCODED_BTS1_TS	6
+#define HARDCODED_BTS2_TS	11
+
+/* reserved according to GSM 03.03 § 2.4 */
+#define GSM_RESERVED_TMSI   0xFFFFFFFF
+
+enum gsm_hooks {
+	GSM_HOOK_NM_SWLOAD,
+	GSM_HOOK_RR_PAGING,
+	GSM_HOOK_RR_SECURITY,
+};
+
+enum gsm_paging_event {
+	GSM_PAGING_SUCCEEDED,
+	GSM_PAGING_EXPIRED,
+	GSM_PAGING_OOM,
+	GSM_PAGING_BUSY,
+};
+
+enum bts_gprs_mode {
+	BTS_GPRS_NONE = 0,
+	BTS_GPRS_GPRS = 1,
+	BTS_GPRS_EGPRS = 2,
+};
+
+#define OBSC_NM_W_ACK_CB(__msgb) (__msgb)->cb[3]
+
+/* the data structure stored in msgb->cb for openbsc apps */
+struct openbsc_msgb_cb {
+	unsigned char *bssgph;
+	unsigned char *llch;
+
+	/* Cell Identifier */
+	unsigned char *bssgp_cell_id;
+
+	/* Identifiers of a BTS, equal to 'struct bssgp_bts_ctx' */
+	u_int16_t nsei;
+	u_int16_t bvci;
+
+	/* Identifier of a MS (inside BTS), equal to 'struct sgsn_mm_ctx' */
+	u_int32_t tlli;
+} __attribute__((packed));
+#define OBSC_MSGB_CB(__msgb)	((struct openbsc_msgb_cb *)&((__msgb)->cb[0]))
+#define msgb_tlli(__x)		OBSC_MSGB_CB(__x)->tlli
+#define msgb_nsei(__x)		OBSC_MSGB_CB(__x)->nsei
+#define msgb_bvci(__x)		OBSC_MSGB_CB(__x)->bvci
+#define msgb_gmmh(__x)		(__x)->l3h
+#define msgb_bssgph(__x)	OBSC_MSGB_CB(__x)->bssgph
+#define msgb_bssgp_len(__x)	((__x)->tail - (uint8_t *)msgb_bssgph(__x))
+#define msgb_bcid(__x)		OBSC_MSGB_CB(__x)->bssgp_cell_id
+#define msgb_llch(__x)		OBSC_MSGB_CB(__x)->llch
+
+#define OBSC_LINKID_CB(__msgb)	(__msgb)->cb[3]
+
+enum gsm_security_event {
+	GSM_SECURITY_NOAVAIL,
+	GSM_SECURITY_AUTH_FAILED,
+	GSM_SECURITY_SUCCEEDED,
+};
+
+struct msgb;
+typedef int gsm_cbfn(unsigned int hooknum,
+		     unsigned int event,
+		     struct msgb *msg,
+		     void *data, void *param);
+
+/* Real authentication information containing Ki */
+enum gsm_auth_algo {
+	AUTH_ALGO_NONE,
+	AUTH_ALGO_XOR,
+	AUTH_ALGO_COMP128v1,
+};
+
+struct gsm_auth_info {
+	enum gsm_auth_algo auth_algo;
+	unsigned int a3a8_ki_len;
+	u_int8_t a3a8_ki[16];
+};
+
+struct gsm_auth_tuple {
+	int use_count;
+	int key_seq;
+	u_int8_t rand[16];
+	u_int8_t sres[4];
+	u_int8_t kc[8];
+};
+#define GSM_KEY_SEQ_INVAL	7	/* GSM 04.08 - 10.5.1.2 */
+
+
+struct gsm_lchan;
+struct gsm_subscriber;
+struct gsm_mncc;
+struct rtp_socket;
+struct bsc_api;
+
+/* Network Management State */
+struct gsm_nm_state {
+	u_int8_t operational;
+	u_int8_t administrative;
+	u_int8_t availability;
+};
+
+/*
+ * LOCATION UPDATING REQUEST state
+ *
+ * Our current operation is:
+ *	- Get imei/tmsi
+ *	- Accept/Reject according to global policy
+ */
+struct gsm_loc_updating_operation {
+        struct timer_list updating_timer;
+	unsigned int waiting_for_imsi : 1;
+	unsigned int waiting_for_imei : 1;
+	unsigned int key_seq : 4;
+};
+
+/*
+ * AUTHENTICATION/CIPHERING state
+ */
+struct gsm_security_operation {
+	struct gsm_auth_tuple atuple;
+	gsm_cbfn *cb;
+	void *cb_data;
+};
+
+/*
+ * A dummy to keep a connection up for at least
+ * a couple of seconds to work around MSC issues.
+ */
+struct gsm_anchor_operation {
+	struct timer_list timeout;
+};
+
+/* Maximum number of neighbor cells whose average we track */
+#define MAX_NEIGH_MEAS		10
+/* Maximum size of the averaging window for neighbor cells */
+#define MAX_WIN_NEIGH_AVG	10
+
+/* processed neighbor measurements for one cell */
+struct neigh_meas_proc {
+	u_int16_t arfcn;
+	u_int8_t bsic;
+	u_int8_t rxlev[MAX_WIN_NEIGH_AVG];
+	unsigned int rxlev_cnt;
+	u_int8_t last_seen_nr;
+};
+
+#define MAX_A5_KEY_LEN	(128/8)
+#define A38_XOR_MIN_KEY_LEN	12
+#define A38_XOR_MAX_KEY_LEN	16
+#define A38_COMP128_KEY_LEN	16
+#define RSL_ENC_ALG_A5(x)	(x+1)
+
+/* is the data link established? who established it? */
+#define LCHAN_SAPI_UNUSED	0
+#define LCHAN_SAPI_MS		1
+#define LCHAN_SAPI_NET		2
+
+/* state of a logical channel */
+enum gsm_lchan_state {
+	LCHAN_S_NONE,		/* channel is not active */
+	LCHAN_S_ACT_REQ,	/* channel activatin requested */
+	LCHAN_S_ACTIVE,		/* channel is active and operational */
+	LCHAN_S_REL_REQ,	/* channel release has been requested */
+	LCHAN_S_REL_ERR,	/* channel is in an error state */
+	LCHAN_S_INACTIVE,	/* channel is set inactive */
+};
+
+/* the per subscriber data for lchan */
+struct gsm_subscriber_connection {
+	struct llist_head entry;
+
+	/* To whom we are allocated at the moment */
+	struct gsm_subscriber *subscr;
+
+	/*
+	 * Operations that have a state and might be pending
+	 */
+	struct gsm_loc_updating_operation *loc_operation;
+	struct gsm_security_operation *sec_operation;
+	struct gsm_anchor_operation *anch_operation;
+
+	/* Are we part of a special "silent" call */
+	int silent_call;
+	int put_channel;
+
+	/* bsc structures */
+	struct osmo_bsc_sccp_con *sccp_con;
+
+	/* back pointers */
+	int in_release;
+	struct gsm_lchan *lchan;
+	struct gsm_lchan *ho_lchan;
+	struct gsm_bts *bts;
+
+	/* for assignment handling */
+	struct timer_list T10;
+	struct gsm_lchan *secondary_lchan;
+
+};
+
+struct gsm_lchan {
+	/* The TS that we're part of */
+	struct gsm_bts_trx_ts *ts;
+	/* The logical subslot number in the TS */
+	u_int8_t nr;
+	/* The logical channel type */
+	enum gsm_chan_t type;
+	/* RSL channel mode */
+	enum rsl_cmod_spd rsl_cmode;
+	/* If TCH, traffic channel mode */
+	enum gsm48_chan_mode tch_mode;
+	/* State */
+	enum gsm_lchan_state state;
+	/* Power levels for MS and BTS */
+	u_int8_t bs_power;
+	u_int8_t ms_power;
+	/* Encryption information */
+	struct {
+		u_int8_t alg_id;
+		u_int8_t key_len;
+		u_int8_t key[MAX_A5_KEY_LEN];
+	} encr;
+
+	struct timer_list T3101;
+	struct timer_list T3111;
+	struct timer_list error_timer;
+
+	/* AMR bits */
+	struct gsm48_multi_rate_conf mr_conf;
+	
+	/* Established data link layer services */
+	u_int8_t sapis[8];
+	int sach_deact;
+	int release_reason;
+
+	/* GSM Random Access data */
+	struct gsm48_req_ref *rqd_ref;
+	uint8_t rqd_ta;
+
+	/* cache of last measurement reports on this lchan */
+	struct gsm_meas_rep meas_rep[6];
+	int meas_rep_idx;
+
+	/* table of neighbor cell measurements */
+	struct neigh_meas_proc neigh_meas[MAX_NEIGH_MEAS];
+
+	struct {
+		u_int32_t bound_ip;
+		u_int32_t connect_ip;
+		u_int16_t bound_port;
+		u_int16_t connect_port;
+		u_int16_t conn_id;
+		u_int8_t rtp_payload;
+		u_int8_t rtp_payload2;
+		u_int8_t speech_mode;
+		struct rtp_socket *rtp_socket;
+	} abis_ip;
+
+	struct gsm_subscriber_connection *conn;
+};
+
+struct gsm_e1_subslot {
+	/* Number of E1 link */
+	u_int8_t	e1_nr;
+	/* Number of E1 TS inside E1 link */
+	u_int8_t	e1_ts;
+	/* Sub-slot within the E1 TS, 0xff if full TS */
+	u_int8_t	e1_ts_ss;
+};
+
+#define TS_F_PDCH_MODE	0x1000
+/* One Timeslot in a TRX */
+struct gsm_bts_trx_ts {
+	struct gsm_bts_trx *trx;
+	/* number of this timeslot at the TRX */
+	u_int8_t nr;
+
+	enum gsm_phys_chan_config pchan;
+
+	unsigned int flags;
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed nm_attr;
+	u_int8_t nm_chan_comb;
+
+	struct {
+		/* Parameters below are configured by VTY */
+		int enabled;
+		u_int8_t maio;
+		u_int8_t hsn;
+		struct bitvec arfcns;
+		u_int8_t arfcns_data[1024/8];
+		/* This is the pre-computed MA for channel assignments */
+		struct bitvec ma;
+		u_int8_t ma_len;	/* part of ma_data that is used */
+		u_int8_t ma_data[8];	/* 10.5.2.21: max 8 bytes value part */
+	} hopping;
+
+	/* To which E1 subslot are we connected */
+	struct gsm_e1_subslot e1_link;
+
+	struct gsm_lchan lchan[TS_MAX_LCHAN];
+};
+
+/* One TRX in a BTS */
+struct gsm_bts_trx {
+	/* list header in bts->trx_list */
+	struct llist_head list;
+
+	struct gsm_bts *bts;
+	/* number of this TRX in the BTS */
+	u_int8_t nr;
+	/* human readable name / description */
+	char *description;
+	/* how do we talk RSL with this TRX? */
+	struct gsm_e1_subslot rsl_e1_link;
+	u_int8_t rsl_tei;
+	struct e1inp_sign_link *rsl_link;
+	/* Some BTS (specifically Ericsson RBS) have a per-TRX OML Link */
+	struct e1inp_sign_link *oml_link;
+
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed nm_attr;
+	struct {
+		struct gsm_nm_state nm_state;
+	} bb_transc;
+
+	u_int16_t arfcn;
+	int nominal_power;		/* in dBm */
+	unsigned int max_power_red;	/* in actual dB */
+
+	union {
+		struct {
+			struct {
+				struct gsm_nm_state nm_state;
+			} bbsig;
+			struct {
+				struct gsm_nm_state nm_state;
+			} pa;
+		} bs11;
+		struct {
+			unsigned int test_state;
+			u_int8_t test_nr;
+			struct rxlev_stats rxlev_stat;
+		} ipaccess;
+	};
+	struct gsm_bts_trx_ts ts[TRX_NR_TS];
+};
+
+#define GSM_BTS_SI(bts, i)	(void *)(bts->si_buf[i])
+
+enum gsm_bts_type {
+	GSM_BTS_TYPE_UNKNOWN,
+	GSM_BTS_TYPE_BS11,
+	GSM_BTS_TYPE_NANOBTS,
+	GSM_BTS_TYPE_RBS2000,
+	GSM_BTS_TYPE_HSL_FEMTO,
+};
+
+struct vty;
+
+struct gsm_bts_model {
+	struct llist_head list;
+
+	enum gsm_bts_type type;
+	const char *name;
+
+	int (*oml_rcvmsg)(struct msgb *msg);
+
+	void (*config_write_bts)(struct vty *vty, struct gsm_bts *bts);
+	void (*config_write_trx)(struct vty *vty, struct gsm_bts_trx *trx);
+	void (*config_write_ts)(struct vty *vty, struct gsm_bts_trx_ts *ts);
+
+	struct tlv_definition nm_att_tlvdef;
+
+	struct bitvec features;
+	uint8_t _features_data[128/8];
+};
+
+enum gsm_bts_features {
+	BTS_FEAT_HSCSD,
+	BTS_FEAT_GPRS,
+	BTS_FEAT_EGPRS,
+	BTS_FEAT_ECSD,
+	BTS_FEAT_HOPPING,
+};
+
+/*
+ * This keeps track of the paging status of one BTS. It
+ * includes a number of pending requests, a back pointer
+ * to the gsm_bts, a timer and some more state.
+ */
+struct gsm_bts_paging_state {
+	/* pending requests */
+	struct llist_head pending_requests;
+	struct gsm_bts *bts;
+
+	struct timer_list work_timer;
+	struct timer_list credit_timer;
+
+	/* free chans needed */
+	int free_chans_need;
+
+	/* load */
+	u_int16_t available_slots;
+};
+
+struct gsm_envabtse {
+	struct gsm_nm_state nm_state;
+};
+
+struct gsm_bts_gprs_nsvc {
+	struct gsm_bts *bts;
+	/* data read via VTY config file, to configure the BTS
+	 * via OML from BSC */
+	int id;
+	u_int16_t nsvci;
+	u_int16_t local_port;	/* on the BTS */
+	u_int16_t remote_port;	/* on the SGSN */
+	u_int32_t remote_ip;	/* on the SGSN */
+
+	struct gsm_nm_state nm_state;
+};
+
+enum neigh_list_manual_mode {
+	NL_MODE_AUTOMATIC = 0,
+	NL_MODE_MANUAL = 1,
+	NL_MODE_MANUAL_SI5SEP = 2, /* SI2 and SI5 have separate neighbor lists */
+};
+
+/* One BTS */
+struct gsm_bts {
+	/* list header in net->bts_list */
+	struct llist_head list;
+
+	struct gsm_network *network;
+	/* number of ths BTS in network */
+	u_int8_t nr;
+	/* human readable name / description */
+	char *description;
+	/* Cell Identity */
+	u_int16_t cell_identity;
+	/* location area code of this BTS */
+	u_int16_t location_area_code;
+	/* Training Sequence Code */
+	u_int8_t tsc;
+	/* Base Station Identification Code (BSIC) */
+	u_int8_t bsic;
+	/* type of BTS */
+	enum gsm_bts_type type;
+	struct gsm_bts_model *model;
+	enum gsm_band band;
+	/* should the channel allocator allocate channels from high TRX to TRX0,
+	 * rather than starting from TRX0 and go upwards? */
+	int chan_alloc_reverse;
+	/* maximum Tx power that the MS is permitted to use in this cell */
+	int ms_max_power;
+
+	/* how do we talk OML with this TRX? */
+	struct gsm_e1_subslot oml_e1_link;
+	u_int8_t oml_tei;
+	struct e1inp_sign_link *oml_link;
+
+	/* Abis network management O&M handle */
+	struct abis_nm_h *nmh;
+	struct gsm_nm_state nm_state;
+	struct tlv_parsed nm_attr;
+
+	/* number of this BTS on given E1 link */
+	u_int8_t bts_nr;
+
+	/* paging state and control */
+	struct gsm_bts_paging_state paging;
+
+	/* CCCH is on C0 */
+	struct gsm_bts_trx *c0;
+
+	struct {
+		struct gsm_nm_state nm_state;
+	} site_mgr;
+
+	enum neigh_list_manual_mode neigh_list_manual_mode;
+	/* parameters from which we build SYSTEM INFORMATION */
+	struct {
+		struct gsm48_rach_control rach_control;
+		u_int8_t ncc_permitted;
+		struct gsm48_cell_sel_par cell_sel_par;
+		struct gsm48_si_selection_params cell_ro_sel_par; /* rest octet */
+		struct gsm48_cell_options cell_options;
+		struct gsm48_control_channel_descr chan_desc;
+		struct bitvec neigh_list;
+		struct bitvec cell_alloc;
+		struct bitvec si5_neigh_list;
+		struct {
+			/* bitmask large enough for all possible ARFCN's */
+			u_int8_t neigh_list[1024/8];
+			u_int8_t cell_alloc[1024/8];
+			/* If the user wants a different neighbor list in SI5 than in SI2 */
+			u_int8_t si5_neigh_list[1024/8];
+		} data;
+	} si_common;
+
+	/* do we use static (user-defined) system information messages? (bitmask) */
+	uint32_t si_mode_static;
+	/* bitmask of all SI that are present/valid in si_buf */
+	uint32_t si_valid;
+	/* buffers where we put the pre-computed SI */
+	sysinfo_buf_t si_buf[_MAX_SYSINFO_TYPE];
+
+	/* ip.accesss Unit ID's have Site/BTS/TRX layout */
+	union {
+		struct {
+			u_int16_t site_id;
+			u_int16_t bts_id;
+			u_int32_t flags;
+		} ip_access;
+		struct {
+			struct {
+				struct gsm_nm_state nm_state;
+			} cclk;
+			struct {
+				struct gsm_nm_state nm_state;
+			} rack;
+			struct gsm_envabtse envabtse[4];
+		} bs11;
+		struct {
+			struct {
+				struct llist_head conn_groups;
+			} is;
+			struct {
+				struct llist_head conn_groups;
+			} con;
+		} rbs2000;
+		struct {
+			unsigned long serno;
+		} hsl;
+	};
+
+	/* Not entirely sure how ip.access specific this is */
+	struct {
+		enum bts_gprs_mode mode;
+		struct {
+			struct gsm_nm_state nm_state;
+			u_int16_t nsei;
+			uint8_t timer[7];
+		} nse;
+		struct {
+			struct gsm_nm_state nm_state;
+			u_int16_t bvci;
+			uint8_t timer[11];
+		} cell;
+		struct gsm_bts_gprs_nsvc nsvc[2];
+		u_int8_t rac;
+	} gprs;
+
+	/* RACH NM values */
+	int rach_b_thresh;
+	int rach_ldavg_slots;
+	
+	/* transceivers */
+	int num_trx;
+	struct llist_head trx_list;
+
+	/* Abis NM queue */
+	struct llist_head abis_queue;
+	int abis_nm_pend;
+};
+
+/* Some statistics of our network */
+struct gsmnet_stats {
+	struct {
+		struct counter *total;
+		struct counter *no_channel;
+	} chreq;
+	struct {
+		struct counter *attempted;
+		struct counter *no_channel;	/* no channel available */
+		struct counter *timeout;		/* T3103 timeout */
+		struct counter *completed;	/* HO COMPL received */
+		struct counter *failed;		/* HO FAIL received */
+	} handover;
+	struct {
+		struct counter *attach;
+		struct counter *normal;
+		struct counter *periodic;
+		struct counter *detach;
+	} loc_upd_type;
+	struct {
+		struct counter *reject;
+		struct counter *accept;
+	} loc_upd_resp;
+	struct {
+		struct counter *attempted;
+		struct counter *detached;
+		struct counter *completed;
+		struct counter *expired;
+	} paging;
+	struct {
+		struct counter *submitted; /* MO SMS submissions */
+		struct counter *no_receiver;
+		struct counter *delivered; /* MT SMS deliveries */
+		struct counter *rp_err_mem;
+		struct counter *rp_err_other;
+	} sms;
+	struct {
+		struct counter *mo_setup;
+		struct counter *mo_connect_ack;
+		struct counter *mt_setup;
+		struct counter *mt_connect;
+	} call;
+	struct {
+		struct counter *rf_fail;
+		struct counter *rll_err;
+	} chan;
+	struct {
+		struct counter *oml_fail;
+		struct counter *rsl_fail;
+	} bts;
+};
+
+enum gsm_auth_policy {
+	GSM_AUTH_POLICY_CLOSED, /* only subscribers authorized in DB */
+	GSM_AUTH_POLICY_ACCEPT_ALL, /* accept everyone, even if not authorized in DB */
+	GSM_AUTH_POLICY_TOKEN, /* accept first, send token per sms, then revoke authorization */
+};
+
+#define GSM_T3101_DEFAULT 10
+#define GSM_T3113_DEFAULT 60
+
+struct gsm_network {
+	/* global parameters */
+	u_int16_t country_code;
+	u_int16_t network_code;
+	char *name_long;
+	char *name_short;
+	enum gsm_auth_policy auth_policy;
+	enum gsm48_reject_value reject_cause;
+	int a5_encryption;
+	int neci;
+	int send_mm_info;
+	struct {
+		int active;
+		/* Window RXLEV averaging */
+		unsigned int win_rxlev_avg;	/* number of SACCH frames */
+		/* Window RXQUAL averaging */
+		unsigned int win_rxqual_avg;	/* number of SACCH frames */
+		/* Window RXLEV neighbouring cells averaging */
+		unsigned int win_rxlev_avg_neigh; /* number of SACCH frames */
+
+		/* how often should we check for power budget HO */
+		unsigned int pwr_interval;	/* SACCH frames */
+		/* how much better does a neighbor cell have to be ? */
+		unsigned int pwr_hysteresis;	/* dBm */
+		/* maximum distacne before we try a handover */
+		unsigned int max_distance;	/* TA values */
+	} handover;
+
+	struct gsmnet_stats stats;
+
+	/* layer 4 */
+	int (*mncc_recv) (struct gsm_network *net, struct msgb *msg);
+	struct llist_head upqueue;
+	struct llist_head trans_list;
+	struct bsc_api *bsc_api;
+
+	unsigned int num_bts;
+	struct llist_head bts_list;
+
+	/* timer values */
+	int T3101;
+	int T3103;
+	int T3105;
+	int T3107;
+	int T3109;
+	int T3111;
+	int T3113;
+	int T3115;
+	int T3117;
+	int T3119;
+	int T3122;
+	int T3141;
+
+	/* Radio Resource Location Protocol (TS 04.31) */
+	struct {
+		enum rrlp_mode mode;
+	} rrlp;
+
+	/* enable the DTXu and DTXd for this network */
+	int dtx_enabled;
+
+	enum gsm_chan_t ctype_by_chreq[16];
+
+	/* Use a TCH for handling requests of type paging any */
+	int pag_any_tch;
+
+	/* MSC data in case we are a true BSC */
+	struct osmo_msc_data *msc_data;
+	int hardcoded_rtp_payload;
+
+	/* subscriber related features */
+	int keep_subscr;
+	struct gsm_sms_queue *sms_queue;
+};
+
+#define SMS_HDR_SIZE	128
+#define SMS_TEXT_SIZE	256
+struct gsm_sms {
+	unsigned long long id;
+	struct gsm_subscriber *sender;
+	struct gsm_subscriber *receiver;
+
+	unsigned long validity_minutes;
+	u_int8_t reply_path_req;
+	u_int8_t status_rep_req;
+	u_int8_t ud_hdr_ind;
+	u_int8_t protocol_id;
+	u_int8_t data_coding_scheme;
+	u_int8_t msg_ref;
+	char dest_addr[20+1];	/* DA LV is 12 bytes max, i.e. 10 bytes
+				 * BCD == 20 bytes string */
+	u_int8_t user_data_len;
+	u_int8_t user_data[SMS_TEXT_SIZE];
+
+	char text[SMS_TEXT_SIZE];
+};
+
+
+struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_code,
+				     int (*mncc_recv)(struct gsm_network *, struct msgb *));
+struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type,
+			      u_int8_t tsc, u_int8_t bsic);
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts);
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type);
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num);
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+				 u_int16_t arfcn, u_int8_t bsic);
+
+struct gsm_bts_trx *gsm_bts_trx_num(struct gsm_bts *bts, int num);
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c);
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name);
+const char *gsm_lchant_name(enum gsm_chan_t c);
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c);
+char *gsm_trx_name(struct gsm_bts_trx *trx);
+char *gsm_ts_name(struct gsm_bts_trx_ts *ts);
+char *gsm_lchan_name(struct gsm_lchan *lchan);
+const char *gsm_lchans_name(enum gsm_lchan_state s);
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, u_int8_t e1_nr,
+		   u_int8_t e1_ts, u_int8_t e1_ts_ss);
+enum gsm_bts_type parse_btstype(const char *arg);
+const char *btstype2str(enum gsm_bts_type type);
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr);
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+				struct gsm_bts *start_bts);
+
+extern void *tall_bsc_ctx;
+extern int ipacc_rtp_direct;
+
+static inline int is_ipaccess_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static inline int is_siemens_bts(struct gsm_bts *bts)
+{
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg);
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy);
+
+enum rrlp_mode rrlp_mode_parse(const char *arg);
+const char *rrlp_mode_name(enum rrlp_mode mode);
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg);
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode);
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked);
+
+int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts);
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts);
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan);
+
+int gsm_btsmodel_set_feature(struct gsm_bts_model *model, enum gsm_bts_features feat);
+int gsm_bts_has_feature(struct gsm_bts *bts, enum gsm_bts_features feat);
+int gsm_bts_model_register(struct gsm_bts_model *model);
+
+struct gsm_subscriber_connection *subscr_con_allocate(struct gsm_lchan *lchan);
+void subscr_con_free(struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/include/openbsc/gsm_subscriber.h b/include/openbsc/gsm_subscriber.h
new file mode 100644
index 0000000..c365bc7
--- /dev/null
+++ b/include/openbsc/gsm_subscriber.h
@@ -0,0 +1,107 @@
+#ifndef _GSM_SUBSCR_H
+#define _GSM_SUBSCR_H
+
+#include <sys/types.h>
+#include "gsm_data.h"
+#include <osmocore/linuxlist.h>
+
+#define GSM_IMEI_LENGTH 17
+#define GSM_IMSI_LENGTH 17
+#define GSM_NAME_LENGTH 160
+
+#define GSM_EXTENSION_LENGTH 15 /* MSISDN can only be 15 digits length */
+#define GSM_MIN_EXTEN 20000
+#define GSM_MAX_EXTEN 49999
+
+#define GSM_SUBSCRIBER_FIRST_CONTACT	0x00000001
+#define tmsi_from_string(str) strtoul(str, NULL, 10)
+
+struct vty;
+
+struct gsm_equipment {
+	long long unsigned int id;
+	char imei[GSM_IMEI_LENGTH];
+	char name[GSM_NAME_LENGTH];
+
+	struct gsm48_classmark1 classmark1;
+	u_int8_t classmark2_len;
+	u_int8_t classmark2[3];
+	u_int8_t classmark3_len;
+	u_int8_t classmark3[14];
+};
+
+struct gsm_subscriber {
+	struct gsm_network *net;
+	long long unsigned int id;
+	char imsi[GSM_IMSI_LENGTH];
+	u_int32_t tmsi;
+	u_int16_t lac;
+	char name[GSM_NAME_LENGTH];
+	char extension[GSM_EXTENSION_LENGTH];
+	int authorized;
+
+	/* Temporary field which is not stored in the DB/HLR */
+	u_int32_t flags;
+
+	/* Every user can only have one equipment in use at any given
+	 * point in time */
+	struct gsm_equipment equipment;
+
+	/* for internal management */
+	int use_count;
+	struct llist_head entry;
+
+	/* pending requests */
+	int in_callback;
+	struct llist_head requests;
+};
+
+enum gsm_subscriber_field {
+	GSM_SUBSCRIBER_IMSI,
+	GSM_SUBSCRIBER_TMSI,
+	GSM_SUBSCRIBER_EXTENSION,
+	GSM_SUBSCRIBER_ID,
+};
+
+enum gsm_subscriber_update_reason {
+	GSM_SUBSCRIBER_UPDATE_ATTACHED,
+	GSM_SUBSCRIBER_UPDATE_DETACHED,
+	GSM_SUBSCRIBER_UPDATE_EQUIPMENT,
+};
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr);
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr);
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net,
+					  u_int32_t tmsi);
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net,
+					  const char *imsi);
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net,
+					       const char *ext);
+struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net,
+					unsigned long long id);
+struct gsm_subscriber *subscr_get_or_create(struct gsm_network *net,
+					const char *imsi);
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason);
+void subscr_put_channel(struct gsm_subscriber *subscr);
+void subscr_get_channel(struct gsm_subscriber *subscr,
+                        int type, gsm_cbfn *cbfn, void *param);
+struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_network *net,
+					     uint32_t tmsi);
+struct gsm_subscriber *subscr_active_by_imsi(struct gsm_network *net,
+					     const char *imsi);
+
+int subscr_pending_requests(struct gsm_subscriber *subscr);
+int subscr_pending_clear(struct gsm_subscriber *subscr);
+int subscr_pending_dump(struct gsm_subscriber *subscr, struct vty *vty);
+int subscr_pending_kick(struct gsm_subscriber *subscr);
+
+char *subscr_name(struct gsm_subscriber *subscr);
+
+int subscr_purge_inactive(struct gsm_network *net);
+void subscr_update_from_db(struct gsm_subscriber *subscr);
+
+/* internal */
+struct gsm_subscriber *subscr_alloc(void);
+extern struct llist_head active_subscribers;
+
+#endif /* _GSM_SUBSCR_H */
diff --git a/include/openbsc/handover.h b/include/openbsc/handover.h
new file mode 100644
index 0000000..9d9a90b
--- /dev/null
+++ b/include/openbsc/handover.h
@@ -0,0 +1,14 @@
+#ifndef _HANDOVER_H
+#define _HANDOVER_H
+
+struct gsm_subscriber_connection;
+
+/* Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm,
+ * after it has decided it wants to initiate HO to a specific BTS */
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts);
+
+/* clear any operation for this connection */
+void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan);
+
+#endif /* _HANDOVER_H */
diff --git a/include/openbsc/ipaccess.h b/include/openbsc/ipaccess.h
new file mode 100644
index 0000000..1d00d97
--- /dev/null
+++ b/include/openbsc/ipaccess.h
@@ -0,0 +1,131 @@
+#ifndef _IPACCESS_H
+#define _IPACCESS_H
+
+#include "e1_input.h"
+#include "gsm_subscriber.h"
+#include <osmocore/linuxlist.h>
+
+#define IPA_TCP_PORT_OML	3002
+#define IPA_TCP_PORT_RSL	3003
+
+struct ipaccess_head {
+	u_int16_t len;	/* network byte order */
+	u_int8_t proto;
+	u_int8_t data[0];
+} __attribute__ ((packed));
+
+struct ipaccess_head_ext {
+	uint8_t proto;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+enum ipaccess_proto {
+	IPAC_PROTO_RSL		= 0x00,
+	IPAC_PROTO_IPACCESS	= 0xfe,
+	IPAC_PROTO_SCCP		= 0xfd,
+	IPAC_PROTO_OML		= 0xff,
+
+
+	/* OpenBSC extensions */
+	IPAC_PROTO_OSMO		= 0xee,
+	IPAC_PROTO_MGCP_OLD	= 0xfc,
+};
+
+enum ipaccess_msgtype {
+	IPAC_MSGT_PING		= 0x00,
+	IPAC_MSGT_PONG		= 0x01,
+	IPAC_MSGT_ID_GET	= 0x04,
+	IPAC_MSGT_ID_RESP	= 0x05,
+	IPAC_MSGT_ID_ACK	= 0x06,
+
+	/* OpenBSC extension */
+	IPAC_MSGT_SCCP_OLD	= 0xff,
+};
+
+enum ipaccess_id_tags {
+	IPAC_IDTAG_SERNR		= 0x00,
+	IPAC_IDTAG_UNITNAME		= 0x01,
+	IPAC_IDTAG_LOCATION1		= 0x02,
+	IPAC_IDTAG_LOCATION2		= 0x03,
+	IPAC_IDTAG_EQUIPVERS		= 0x04,
+	IPAC_IDTAG_SWVERSION		= 0x05,
+	IPAC_IDTAG_IPADDR		= 0x06,
+	IPAC_IDTAG_MACADDR		= 0x07,
+	IPAC_IDTAG_UNIT			= 0x08,
+};
+
+struct ipac_msgt_sccp_state {
+	uint8_t	src_ref[3];
+	uint8_t	dst_ref[3];
+	uint8_t trans_id;
+	uint8_t invoke_id;
+	char		imsi[GSM_IMSI_LENGTH];
+} __attribute__((packed));
+
+int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa);
+
+/*
+ * methods for parsing and sending a message
+ */
+int ipaccess_rcvmsg_base(struct msgb *msg, struct bsc_fd *bfd);
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error);
+void ipaccess_prepend_header(struct msgb *msg, int proto);
+int ipaccess_send_id_ack(int fd);
+int ipaccess_send_id_req(int fd);
+
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len);
+
+int ipaccess_drop_oml(struct gsm_bts *bts);
+int ipaccess_drop_rsl(struct gsm_bts_trx *trx);
+
+/*
+ * Firmware specific header
+ */
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[2];
+	u_int16_t more_more_magic;
+	u_int32_t header_length;
+	u_int32_t file_length;
+	char sw_part[20];
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	u_int16_t table_offset;
+	/* stuff i don't know */
+} __attribute__((packed));
+
+struct sdp_header_entry {
+	u_int16_t something1;
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	u_int32_t length;
+	u_int32_t addr1;
+	u_int32_t addr2;
+	u_int32_t start;
+} __attribute__((packed));
+
+struct sdp_header_item {
+	struct sdp_header_entry header_entry;
+	struct llist_head entry;
+	off_t absolute_offset;
+};
+
+struct sdp_header {
+	struct sdp_firmware firmware_info;
+
+	/* for more_magic a list of sdp_header_entry_list */
+	struct llist_head header_list;
+
+	/* the entry of the sdp_header */
+	struct llist_head entry;
+};
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned base_offset, struct llist_head *list);
+
+#endif /* _IPACCESS_H */
diff --git a/include/openbsc/meas_rep.h b/include/openbsc/meas_rep.h
new file mode 100644
index 0000000..3c2c8d1
--- /dev/null
+++ b/include/openbsc/meas_rep.h
@@ -0,0 +1,85 @@
+#ifndef _MEAS_REP_H
+#define _MEAS_REP_H
+
+#define MRC_F_PROCESSED	0x0001
+
+/* extracted from a L3 measurement report IE */
+struct gsm_meas_rep_cell {
+	u_int8_t rxlev;
+	u_int8_t bsic;
+	u_int8_t neigh_idx;
+	u_int16_t arfcn;
+	unsigned int flags;
+};
+
+/* RX Level and RX Quality */
+struct gsm_rx_lev_qual {
+	u_int8_t rx_lev;
+	u_int8_t rx_qual;
+};
+
+/* unidirectional measumrement report */
+struct gsm_meas_rep_unidir {
+	struct gsm_rx_lev_qual full;
+	struct gsm_rx_lev_qual sub;
+};
+
+#define MEAS_REP_F_UL_DTX	0x01
+#define MEAS_REP_F_DL_VALID	0x02
+#define MEAS_REP_F_BA1		0x04
+#define MEAS_REP_F_DL_DTX	0x08
+#define MEAS_REP_F_MS_TO	0x10
+#define MEAS_REP_F_MS_L1	0x20
+#define MEAS_REP_F_FPC		0x40
+
+/* parsed uplink and downlink measurement result */
+struct gsm_meas_rep {
+	/* back-pointer to the logical channel */
+	struct gsm_lchan *lchan;
+
+	/* number of the measurement report */
+	u_int8_t nr;
+	/* flags, see MEAS_REP_F_* */
+	unsigned int flags;
+
+	/* uplink and downlink rxlev, rxqual; full and sub */
+	struct gsm_meas_rep_unidir ul;
+	struct gsm_meas_rep_unidir dl;
+
+	u_int8_t bs_power;
+	u_int8_t ms_timing_offset;
+	struct {
+		int8_t pwr;	/* MS power in dBm */
+		u_int8_t ta;	/* MS timing advance */
+	} ms_l1;
+
+	/* neighbor measurement reports for up to 6 cells */
+	int num_cell;
+	struct gsm_meas_rep_cell cell[6];
+};
+
+enum meas_rep_field {
+	MEAS_REP_DL_RXLEV_FULL,
+	MEAS_REP_DL_RXLEV_SUB,
+	MEAS_REP_DL_RXQUAL_FULL,
+	MEAS_REP_DL_RXQUAL_SUB,
+	MEAS_REP_UL_RXLEV_FULL,
+	MEAS_REP_UL_RXLEV_SUB,
+	MEAS_REP_UL_RXQUAL_FULL,
+	MEAS_REP_UL_RXQUAL_SUB,
+};
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+		     enum meas_rep_field field, unsigned int num);
+
+/* Check if N out of M last values for FIELD are >= bd */
+int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
+			enum meas_rep_field field,
+			unsigned int n, unsigned int m, int be);
+
+unsigned int calc_initial_idx(unsigned int array_size,
+			      unsigned int meas_rep_idx,
+			      unsigned int num_values);
+
+#endif /* _MEAS_REP_H */
diff --git a/include/openbsc/mgcp.h b/include/openbsc/mgcp.h
new file mode 100644
index 0000000..516b76e
--- /dev/null
+++ b/include/openbsc/mgcp.h
@@ -0,0 +1,194 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+
+/*
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_MGCP_H
+#define OPENBSC_MGCP_H
+
+#include <osmocore/msgb.h>
+#include <osmocore/write_queue.h>
+
+#include "debug.h"
+
+#include <arpa/inet.h>
+
+#define RTP_PORT_DEFAULT 4000
+#define RTP_PORT_NET_DEFAULT 16000
+
+/**
+ * Calculate the RTP audio port for the given multiplex
+ * and the direction. This allows a semi static endpoint
+ * to port calculation removing the need for the BSC
+ * and the MediaGateway to communicate.
+ *
+ * Port usage explained:
+ *       base + (multiplex * 2) + 0 == local port to wait for network packets
+ *       base + (multiplex * 2) + 1 == local port for rtcp
+ *
+ * The above port will receive packets from the BTS that need
+ * to be patched and forwarded to the network.
+ * The above port will receive packets from the network that
+ * need to be patched and forwarded to the BTS.
+ *
+ * We assume to have a static BTS IP address so we can differentiate
+ * network and BTS.
+ *
+ */
+static inline int rtp_calculate_port(int multiplex, int base)
+{
+	return base + (multiplex * 2);
+}
+
+
+/*
+ * Handling of MGCP Endpoints and the MGCP Config
+ */
+struct mgcp_endpoint;
+struct mgcp_config;
+struct mgcp_trunk_config;
+
+#define MGCP_ENDP_CRCX 1
+#define MGCP_ENDP_DLCX 2
+#define MGCP_ENDP_MDCX 3
+
+/*
+ * what to do with the msg?
+ *	- continue as usual?
+ *	- reject and send a failure code?
+ *	- defer? do not send anything
+ */
+#define MGCP_POLICY_CONT	4
+#define MGCP_POLICY_REJECT	5
+#define MGCP_POLICY_DEFER	6
+
+typedef int (*mgcp_realloc)(struct mgcp_trunk_config *cfg, int endpoint);
+typedef int (*mgcp_change)(struct mgcp_trunk_config *cfg, int endpoint, int state);
+typedef int (*mgcp_policy)(struct mgcp_trunk_config *cfg, int endpoint, int state, const char *transactio_id);
+typedef int (*mgcp_reset)(struct mgcp_config *cfg);
+
+#define PORT_ALLOC_STATIC	0
+#define PORT_ALLOC_DYNAMIC	1
+
+/**
+ * This holds information on how to allocate ports
+ */
+struct mgcp_port_range {
+	int mode;
+
+	/* pre-allocated from a base? */
+	int base_port;
+
+	/* dynamically allocated */
+	int range_start;
+	int range_end;
+	int last_port;
+};
+
+struct mgcp_trunk_config {
+	struct llist_head entry;
+
+	struct mgcp_config *cfg;
+
+	int trunk_nr;
+	int trunk_type;
+
+	char *audio_name;
+	int audio_payload;
+	int audio_loop;
+
+	/* spec handling */
+	int force_realloc;
+
+	unsigned int number_endpoints;
+	struct mgcp_endpoint *endpoints;
+};
+
+struct mgcp_config {
+	int source_port;
+	char *local_ip;
+	char *source_addr;
+	char *bts_ip;
+	char *call_agent_addr;
+
+	struct in_addr bts_in;
+
+	/* transcoder handling */
+	char *transcoder_ip;
+	struct in_addr transcoder_in;
+	int transcoder_remote_base;
+
+	struct write_queue gw_fd;
+
+	struct mgcp_port_range bts_ports;
+	struct mgcp_port_range net_ports;
+	struct mgcp_port_range transcoder_ports;
+	int endp_dscp;
+
+	mgcp_change change_cb;
+	mgcp_policy policy_cb;
+	mgcp_reset reset_cb;
+	mgcp_realloc realloc_cb;
+	void *data;
+
+	uint32_t last_call_id;
+
+	/* trunk handling */
+	struct mgcp_trunk_config trunk;
+	struct llist_head trunks;
+
+	/* only used for start with a static configuration */
+	int last_net_port;
+	int last_bts_port;
+};
+
+/* config management */
+struct mgcp_config *mgcp_config_alloc(void);
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg);
+int mgcp_vty_init(void);
+int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg);
+void mgcp_free_endp(struct mgcp_endpoint *endp);
+int mgcp_reset_transcoder(struct mgcp_config *cfg);
+
+/*
+ * format helper functions
+ */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg);
+struct msgb *mgcp_create_response_with_data(int code, const char *txt, const char *msg, const char *trans, const char *data);
+
+/* adc helper */
+static inline int mgcp_timeslot_to_endpoint(int multiplex, int timeslot)
+{
+	if (timeslot == 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Timeslot should not be 0\n");
+		timeslot = 255;
+	}
+
+	return timeslot + (32 * multiplex);
+}
+
+static inline void mgcp_endpoint_to_timeslot(int endpoint, int *multiplex, int *timeslot)
+{
+	*multiplex = endpoint / 32;
+	*timeslot = endpoint % 32;
+}
+
+
+#endif
diff --git a/include/openbsc/mgcp_internal.h b/include/openbsc/mgcp_internal.h
new file mode 100644
index 0000000..7c6bb54
--- /dev/null
+++ b/include/openbsc/mgcp_internal.h
@@ -0,0 +1,154 @@
+/* MGCP Private Data */
+
+/*
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_MGCP_DATA_H
+#define OPENBSC_MGCP_DATA_H
+
+#include <osmocore/select.h>
+
+#define CI_UNUSED 0
+
+enum mgcp_connection_mode {
+	MGCP_CONN_NONE = 0,
+	MGCP_CONN_RECV_ONLY = 1,
+	MGCP_CONN_SEND_ONLY = 2,
+	MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY,
+	MGCP_CONN_LOOPBACK  = 4,
+};
+
+enum mgcp_trunk_type {
+	MGCP_TRUNK_VIRTUAL,
+	MGCP_TRUNK_E1,
+};
+
+struct mgcp_rtp_state {
+	int initialized;
+	int patch;
+
+	uint32_t orig_ssrc;
+	uint32_t ssrc;
+	uint16_t seq_no;
+	int lost_no;
+	int seq_offset;
+	uint32_t last_timestamp;
+	int32_t  timestamp_offset;
+};
+
+struct mgcp_rtp_end {
+	/* statistics */
+	unsigned int packets;
+	struct in_addr addr;
+
+	/* in network byte order */
+	int rtp_port, rtcp_port;
+
+	int payload_type;
+
+	/*
+	 * Each end has a socket...
+	 */
+	struct bsc_fd rtp;
+	struct bsc_fd rtcp;
+
+	int local_port;
+	int local_alloc;
+};
+
+enum {
+	MGCP_TAP_BTS_IN,
+	MGCP_TAP_BTS_OUT,
+	MGCP_TAP_NET_IN,
+	MGCP_TAP_NET_OUT,
+
+	/* last element */
+	MGCP_TAP_COUNT
+};
+
+struct mgcp_rtp_tap {
+	int enabled;
+	struct sockaddr_in forward;
+};
+
+struct mgcp_endpoint {
+	int allocated;
+	uint32_t ci;
+	char *callid;
+	char *local_options;
+	int conn_mode;
+	int orig_mode;
+
+	/* backpointer */
+	struct mgcp_config *cfg;
+	struct mgcp_trunk_config *tcfg;
+
+	/* port status for bts/net */
+	struct mgcp_rtp_end bts_end;
+	struct mgcp_rtp_end net_end;
+
+	/*
+	 * For transcoding we will send from the local_port
+	 * of trans_bts and it will arrive at trans_net from
+	 * where we will forward it to the network.
+	 */
+	struct mgcp_rtp_end trans_bts;
+	struct mgcp_rtp_end trans_net;
+	int is_transcoded;
+
+	/* sequence bits */
+	struct mgcp_rtp_state net_state;
+	struct mgcp_rtp_state bts_state;
+
+	/* SSRC/seq/ts patching for loop */
+	int allow_patch;
+
+	/* tap for the endpoint */
+	struct mgcp_rtp_tap taps[MGCP_TAP_COUNT];
+};
+
+#define ENDPOINT_NUMBER(endp) abs(endp - endp->tcfg->endpoints)
+
+struct mgcp_msg_ptr {
+	unsigned int start;
+	unsigned int length;
+};
+
+int mgcp_analyze_header(struct mgcp_config *cfg, struct msgb *msg,
+			struct mgcp_msg_ptr *ptr, int size,
+			const char **transaction_id, struct mgcp_endpoint **endp);
+int mgcp_send_dummy(struct mgcp_endpoint *endp);
+int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port);
+int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port);
+int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *enp, int rtp_port);
+int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *enp, int rtp_port);
+int mgcp_free_rtp_port(struct mgcp_rtp_end *end);
+
+/* For transcoding we need to manage an in and an output that are connected */
+static inline int endp_back_channel(int endpoint)
+{
+	return endpoint + 60;
+}
+
+struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index);
+struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index);
+
+
+#endif
diff --git a/include/openbsc/misdn.h b/include/openbsc/misdn.h
new file mode 100644
index 0000000..0a8b063
--- /dev/null
+++ b/include/openbsc/misdn.h
@@ -0,0 +1,27 @@
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MISDN_H
+#define MISDN_H
+
+#include "e1_input.h"
+
+int mi_setup(int cardnr,  struct e1inp_line *line, int release_l2);
+int mi_e1_line_update(struct e1inp_line *line);
+
+#endif
diff --git a/include/openbsc/mncc.h b/include/openbsc/mncc.h
new file mode 100644
index 0000000..e514c19
--- /dev/null
+++ b/include/openbsc/mncc.h
@@ -0,0 +1,172 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface 
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _MNCC_H
+#define _MNCC_H
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/mncc.h>
+
+#include <stdint.h>
+
+struct gsm_network;
+struct msgb;
+
+
+/* One end of a call */
+struct gsm_call {
+	struct llist_head entry;
+
+	/* network handle */
+	void *net;
+
+	/* the 'local' transaction */
+	uint32_t callref;
+	/* the 'remote' transaction */
+	uint32_t remote_ref;
+};
+
+#define MNCC_SETUP_REQ		0x0101
+#define MNCC_SETUP_IND		0x0102
+#define MNCC_SETUP_RSP		0x0103
+#define MNCC_SETUP_CNF		0x0104
+#define MNCC_SETUP_COMPL_REQ	0x0105
+#define MNCC_SETUP_COMPL_IND	0x0106
+/* MNCC_REJ_* is perfomed via MNCC_REL_* */
+#define MNCC_CALL_CONF_IND	0x0107
+#define MNCC_CALL_PROC_REQ	0x0108
+#define MNCC_PROGRESS_REQ	0x0109
+#define MNCC_ALERT_REQ		0x010a
+#define MNCC_ALERT_IND		0x010b
+#define MNCC_NOTIFY_REQ		0x010c
+#define MNCC_NOTIFY_IND		0x010d
+#define MNCC_DISC_REQ		0x010e
+#define MNCC_DISC_IND		0x010f
+#define MNCC_REL_REQ		0x0110
+#define MNCC_REL_IND		0x0111
+#define MNCC_REL_CNF		0x0112
+#define MNCC_FACILITY_REQ	0x0113
+#define MNCC_FACILITY_IND	0x0114
+#define MNCC_START_DTMF_IND	0x0115
+#define MNCC_START_DTMF_RSP	0x0116
+#define MNCC_START_DTMF_REJ	0x0117
+#define MNCC_STOP_DTMF_IND	0x0118
+#define MNCC_STOP_DTMF_RSP	0x0119
+#define MNCC_MODIFY_REQ		0x011a
+#define MNCC_MODIFY_IND		0x011b
+#define MNCC_MODIFY_RSP		0x011c
+#define MNCC_MODIFY_CNF		0x011d
+#define MNCC_MODIFY_REJ		0x011e
+#define MNCC_HOLD_IND		0x011f
+#define MNCC_HOLD_CNF		0x0120
+#define MNCC_HOLD_REJ		0x0121
+#define MNCC_RETRIEVE_IND	0x0122
+#define MNCC_RETRIEVE_CNF	0x0123
+#define MNCC_RETRIEVE_REJ	0x0124
+#define MNCC_USERINFO_REQ	0x0125
+#define MNCC_USERINFO_IND	0x0126
+#define MNCC_REJ_REQ		0x0127
+#define MNCC_REJ_IND		0x0128
+
+#define MNCC_BRIDGE		0x0200
+#define MNCC_FRAME_RECV		0x0201
+#define MNCC_FRAME_DROP		0x0202
+#define MNCC_LCHAN_MODIFY	0x0203
+
+#define GSM_TCHF_FRAME		0x0300
+#define GSM_TCHF_FRAME_EFR	0x0301
+
+#define GSM_MAX_FACILITY	128
+#define GSM_MAX_SSVERSION	128
+#define GSM_MAX_USERUSER	128
+
+#define	MNCC_F_BEARER_CAP	0x0001
+#define MNCC_F_CALLED		0x0002
+#define MNCC_F_CALLING		0x0004
+#define MNCC_F_REDIRECTING	0x0008
+#define MNCC_F_CONNECTED	0x0010
+#define MNCC_F_CAUSE		0x0020
+#define MNCC_F_USERUSER		0x0040
+#define MNCC_F_PROGRESS		0x0080
+#define MNCC_F_EMERGENCY	0x0100
+#define MNCC_F_FACILITY		0x0200
+#define MNCC_F_SSVERSION	0x0400
+#define MNCC_F_CCCAP		0x0800
+#define MNCC_F_KEYPAD		0x1000
+#define MNCC_F_SIGNAL		0x2000
+
+struct gsm_mncc {
+	/* context based information */
+	uint32_t	msg_type;
+	uint32_t	callref;
+
+	/* which fields are present */
+	uint32_t	fields;
+
+	/* data derived informations (MNCC_F_ based) */
+	struct gsm_mncc_bearer_cap	bearer_cap;
+	struct gsm_mncc_number		called;
+	struct gsm_mncc_number		calling;
+	struct gsm_mncc_number		redirecting;
+	struct gsm_mncc_number		connected;
+	struct gsm_mncc_cause		cause;
+	struct gsm_mncc_progress	progress;
+	struct gsm_mncc_useruser	useruser;
+	struct gsm_mncc_facility	facility;
+	struct gsm_mncc_cccap		cccap;
+	struct gsm_mncc_ssversion	ssversion;
+	struct	{
+		int		sup;
+		int		inv;
+	} clir;
+	int		signal;
+
+	/* data derived information, not MNCC_F based */
+	int		keypad;
+	int		more;
+	int		notify; /* 0..127 */
+	int		emergency;
+	char		imsi[16];
+
+	unsigned char	lchan_mode;
+};
+
+struct gsm_data_frame {
+	uint32_t	msg_type;
+	uint32_t	callref;
+	unsigned char	data[0];
+};
+
+char *get_mncc_name(int value);
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg);
+
+/* input from CC code into mncc_builtin */
+int int_mncc_recv(struct gsm_network *net, struct msgb *msg);
+
+/* input from CC code into mncc_sock */
+int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg);
+
+int mncc_sock_init(struct gsm_network *gsmnet);
+
+#endif
diff --git a/include/openbsc/network_listen.h b/include/openbsc/network_listen.h
new file mode 100644
index 0000000..67d1f4e
--- /dev/null
+++ b/include/openbsc/network_listen.h
@@ -0,0 +1,16 @@
+#ifndef _OPENBSC_NWL_H
+#define _OPENBSC_NWL_H
+
+#include <stdint.h>
+#include <openbsc/gsm_data.h>
+
+void ipac_nwl_init(void);
+
+/* Start a NWL test.  It will raise the S_IPAC_TEST_COMPLETE signal. */
+int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr,
+			const uint8_t *phys_conf, unsigned int phys_conf_len);
+
+int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev,
+			     uint16_t max_num_arfcns);
+
+#endif /* _OPENBSC_NWL_H */
diff --git a/include/openbsc/openbscdefines.h b/include/openbsc/openbscdefines.h
new file mode 100644
index 0000000..c6ac153
--- /dev/null
+++ b/include/openbsc/openbscdefines.h
@@ -0,0 +1,34 @@
+/* 
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSCDEFINES_H
+#define OPENBSCDEFINES_H
+
+#ifdef BUILDING_ON_WINDOWS
+    #ifdef BUILDING_OPENBSC
+        #define BSC_API __declspec(dllexport)
+    #else
+        #define BSC_API __declspec(dllimport)
+    #endif
+#else
+    #define BSC_API __attribute__((visibility("default")))
+#endif
+
+#endif
diff --git a/include/openbsc/osmo_bsc.h b/include/openbsc/osmo_bsc.h
new file mode 100644
index 0000000..ef0f11a
--- /dev/null
+++ b/include/openbsc/osmo_bsc.h
@@ -0,0 +1,43 @@
+/* OpenBSC BSC code */
+
+#ifndef OSMO_BSC_H
+#define OSMO_BSC_H
+
+#include "bsc_api.h"
+
+struct sccp_connection;
+
+struct osmo_bsc_sccp_con {
+	struct llist_head entry;
+
+	int ciphering_handled;
+	int rtp_port;
+
+	/* SCCP connection realted */
+	struct sccp_connection *sccp;
+	struct bsc_msc_connection *msc_con;
+	struct timer_list sccp_it_timeout;
+	struct timer_list sccp_cc_timeout;
+
+	struct llist_head sccp_queue;
+	unsigned int sccp_queue_size;
+
+	struct gsm_subscriber_connection *conn;
+	uint8_t new_subscriber;
+};
+
+struct bsc_api *osmo_bsc_api();
+
+int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg);
+int bsc_open_connection(struct osmo_bsc_sccp_con *sccp, struct msgb *msg);
+int bsc_create_new_connection(struct gsm_subscriber_connection *conn);
+int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp);
+
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg);
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+int bsc_handle_udt(struct gsm_network *net, struct bsc_msc_connection *conn, struct msgb *msg, unsigned int length);
+int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn, struct msgb *msg, unsigned int len);
+
+
+#endif
diff --git a/include/openbsc/osmo_bsc_grace.h b/include/openbsc/osmo_bsc_grace.h
new file mode 100644
index 0000000..45d4db8
--- /dev/null
+++ b/include/openbsc/osmo_bsc_grace.h
@@ -0,0 +1,28 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OSMO_BSC_GRACE_H
+#define OSMO_BSC_GRACE_H
+
+#include "gsm_data.h"
+
+int bsc_grace_allow_new_connection(struct gsm_network *network);
+
+#endif
diff --git a/include/openbsc/osmo_bsc_rf.h b/include/openbsc/osmo_bsc_rf.h
new file mode 100644
index 0000000..e43ae2e
--- /dev/null
+++ b/include/openbsc/osmo_bsc_rf.h
@@ -0,0 +1,35 @@
+#ifndef OSMO_BSC_RF
+#define OSMO_BSC_RF
+
+#include <osmocore/write_queue.h>
+#include <osmocore/timer.h>
+
+struct gsm_network;
+
+struct osmo_bsc_rf {
+	/* the value of signal.h */
+	int policy;
+	struct bsc_fd listen;
+	struct gsm_network *gsm_network;
+
+	const char *last_state_command;
+
+	/* delay the command */
+	char last_request;
+	struct timer_list delay_cmd;
+
+	/* verify that RF is up as it should be */
+	struct timer_list rf_check;
+
+	/* some handling for the automatic grace switch */
+	struct timer_list grace_timeout;
+};
+
+struct osmo_bsc_rf_conn {
+	struct write_queue queue;
+	struct osmo_bsc_rf *rf;
+};
+
+struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net);
+
+#endif
diff --git a/include/openbsc/osmo_msc.h b/include/openbsc/osmo_msc.h
new file mode 100644
index 0000000..beb3f5e
--- /dev/null
+++ b/include/openbsc/osmo_msc.h
@@ -0,0 +1,11 @@
+/* Routines for the MSC handling */
+
+#ifndef OSMO_MSC_H
+#define OSMO_MSC_H
+
+#include "bsc_api.h"
+
+struct bsc_api *msc_bsc_api();
+void msc_release_connection(struct gsm_subscriber_connection *conn);
+
+#endif
diff --git a/include/openbsc/osmo_msc_data.h b/include/openbsc/osmo_msc_data.h
new file mode 100644
index 0000000..8f9ca68
--- /dev/null
+++ b/include/openbsc/osmo_msc_data.h
@@ -0,0 +1,76 @@
+/*
+ * Data for the true BSC
+ *
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _OSMO_MSC_DATA_H
+#define _OSMO_MSC_DATA_H
+
+#include "bsc_msc.h"
+
+#include <osmocore/timer.h>
+
+struct osmo_bsc_rf;
+struct gsm_network;
+
+struct gsm_audio_support {
+        uint8_t hr  : 1,
+                ver : 7;
+};
+
+struct osmo_msc_data {
+	/* Connection data */
+	char *bsc_token;
+	int msc_port;
+	int msc_ip_dscp;
+	char *msc_ip;
+	int ping_timeout;
+	int pong_timeout;
+	struct timer_list ping_timer;
+	struct timer_list pong_timer;
+	struct bsc_msc_connection *msc_con;
+	int core_ncc;
+	int core_mcc;
+	int rtp_base;
+
+	/* audio codecs */
+	struct gsm_audio_support **audio_support;
+	int audio_length;
+
+
+	/* mgcp agent */
+	struct write_queue mgcp_agent;
+
+	/* rf ctl related bits */
+	char *mid_call_txt;
+	int mid_call_timeout;
+	struct osmo_bsc_rf *rf_ctl;
+
+	/* ussd welcome text */
+	char *ussd_welcome_txt;
+};
+
+int osmo_bsc_msc_init(struct gsm_network *network);
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet);
+int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto);
+
+int osmo_bsc_audio_init(struct gsm_network *network);
+
+#endif
diff --git a/include/openbsc/paging.h b/include/openbsc/paging.h
new file mode 100644
index 0000000..f719199
--- /dev/null
+++ b/include/openbsc/paging.h
@@ -0,0 +1,71 @@
+/* Paging helper and manager.... */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef PAGING_H
+#define PAGING_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocore/linuxlist.h>
+#include "gsm_data.h"
+#include "gsm_subscriber.h"
+#include <osmocore/timer.h>
+
+/**
+ * A pending paging request
+ */
+struct gsm_paging_request {
+	/* list_head for list of all paging requests */
+	struct llist_head entry;
+	/* the subscriber which we're paging. Later gsm_paging_request
+	 * should probably become a part of the gsm_subscriber struct? */
+	struct gsm_subscriber *subscr;
+	/* back-pointer to the BTS on which we are paging */
+	struct gsm_bts *bts;
+	/* what kind of channel type do we ask the MS to establish */
+	int chan_type;
+
+	/* Timer 3113: how long do we try to page? */
+	struct timer_list T3113;
+
+	/* How often did we ask the BTS to page? */
+	int attempts;
+
+	/* callback to be called in case paging completes */
+	gsm_cbfn *cbfn;
+	void *cbfn_param;
+};
+
+/* call once for every gsm_bts... */
+void paging_init(struct gsm_bts *bts);
+
+/* schedule paging request */
+int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr,
+		   int type, gsm_cbfn *cbfn, void *data);
+
+/* stop paging requests */
+void paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+			 struct gsm_subscriber_connection *conn,
+			 struct msgb *msg);
+
+/* update paging load */
+void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t);
+
+#endif
diff --git a/include/openbsc/rest_octets.h b/include/openbsc/rest_octets.h
new file mode 100644
index 0000000..6d90119
--- /dev/null
+++ b/include/openbsc/rest_octets.h
@@ -0,0 +1,133 @@
+#ifndef _REST_OCTETS_H
+#define _REST_OCTETS_H
+
+#include <sys/types.h>
+#include <openbsc/gsm_04_08.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos);
+
+struct gsm48_si_selection_params {
+	u_int16_t penalty_time:5,
+		  temp_offs:3,
+		  cell_resel_off:6,
+		  cbq:1,
+		  present:1;
+};
+
+struct gsm48_si_power_offset {
+	u_int8_t power_offset:2,
+		 present:1;
+};
+
+struct gsm48_si3_gprs_ind {
+	u_int8_t si13_position:1,
+		 ra_colour:3,
+		 present:1;
+};
+
+struct gsm48_lsa_params {
+	u_int32_t prio_thr:3,
+		 lsa_offset:3,
+		 mcc:12,
+		 mnc:12;
+	unsigned int present;
+};
+
+struct gsm48_si_ro_info {
+	struct gsm48_si_selection_params selection_params;
+	struct gsm48_si_power_offset power_offset;
+	u_int8_t si2ter_indicator;
+	u_int8_t early_cm_ctrl;
+	struct {
+		u_int8_t where:3,
+			 present:1;
+	} scheduling;
+	struct gsm48_si3_gprs_ind gprs_ind;
+
+	/* SI 4 specific */
+	struct gsm48_lsa_params lsa_params;
+	u_int16_t cell_id;
+	u_int8_t break_ind;	/* do we have SI7 + SI8 ? */
+};
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(u_int8_t *data, const struct gsm48_si_ro_info *si3);
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(u_int8_t *data, const struct gsm48_si_ro_info *si4);
+
+enum pbcch_carrier_type {
+	PBCCH_BCCH,
+	PBCCH_ARFCN,
+	PBCCH_MAIO
+};
+
+/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
+enum gprs_nmo {
+	GPRS_NMO_I	= 0,	/* CS pagin on GPRS paging or traffic channel */
+	GPRS_NMO_II	= 1,	/* all paging on CCCH */
+	GPRS_NMO_III	= 2,	/* no paging coordination */
+};
+
+/* TS 04.60 12.24 */
+struct gprs_cell_options {
+	enum gprs_nmo nmo;
+	/* T3168: wait for packet uplink assignment message */
+	u_int32_t t3168;	/* in milliseconds */
+	/* T3192: wait for release of the TBF after reception of the final block */
+	u_int32_t t3192;	/* in milliseconds */
+	u_int32_t drx_timer_max;/* in seconds */
+	u_int32_t bs_cv_max;
+
+	u_int8_t ext_info_present;
+	struct {
+		u_int8_t egprs_supported;
+			u_int8_t use_egprs_p_ch_req;
+			u_int8_t bep_period;
+		u_int8_t pfc_supported;
+		u_int8_t dtm_supported;
+		u_int8_t bss_paging_coordination;
+	} ext_info;
+};
+
+/* TS 04.60 Table 12.9.2 */
+struct gprs_power_ctrl_pars {
+	u_int8_t alpha;
+	u_int8_t t_avg_w;
+	u_int8_t t_avg_t;
+	u_int8_t pc_meas_chan;
+	u_int8_t n_avg_i;
+};
+
+struct gsm48_si13_info {
+	struct gprs_cell_options cell_opts;
+	struct gprs_power_ctrl_pars pwr_ctrl_pars;
+	u_int8_t bcch_change_mark;
+	u_int8_t si_change_field;
+	u_int8_t pbcch_present;
+
+	union {
+		struct {
+			u_int8_t rac;
+			u_int8_t spgc_ccch_sup;
+			u_int8_t net_ctrl_ord;
+			u_int8_t prio_acc_thr;
+		} no_pbcch;
+		struct {
+			u_int8_t psi1_rep_per;
+			u_int8_t pb;
+			u_int8_t tsc;
+			u_int8_t tn;
+			enum pbcch_carrier_type carrier_type;
+			u_int16_t arfcn;
+			u_int8_t maio;
+		} pbcch;
+	};
+};
+
+/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13);
+
+#endif /* _REST_OCTETS_H */
diff --git a/include/openbsc/rs232.h b/include/openbsc/rs232.h
new file mode 100644
index 0000000..61187ca
--- /dev/null
+++ b/include/openbsc/rs232.h
@@ -0,0 +1,9 @@
+#ifndef _RS232_H
+#define _RS232_H
+
+int rs232_setup(const char *serial_port, unsigned int delay_ms,
+		struct gsm_bts *bts);
+
+int handle_serial_msg(struct msgb *msg);
+
+#endif /* _RS232_H */
diff --git a/include/openbsc/rtp_proxy.h b/include/openbsc/rtp_proxy.h
new file mode 100644
index 0000000..53b58b4
--- /dev/null
+++ b/include/openbsc/rtp_proxy.h
@@ -0,0 +1,90 @@
+#ifndef _RTP_PROXY_H
+#define _RTP_PROXY_H
+
+/* RTP proxy handling for ip.access nanoBTS */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <netinet/in.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/select.h>
+
+#define RTP_PT_GSM_FULL 3
+#define RTP_PT_GSM_HALF 96
+#define RTP_PT_GSM_EFR 97
+#define RTP_PT_AMR_FULL 98
+#define RTP_PT_AMR_HALF 99
+
+enum rtp_rx_action {
+	RTP_NONE,
+	RTP_PROXY,
+	RTP_RECV_UPSTREAM,
+};
+
+enum rtp_tx_action {
+	RTP_SEND_NONE,
+	RTP_SEND_DOWNSTREAM,
+};
+
+struct rtp_sub_socket {
+	struct sockaddr_in sin_local;
+	struct sockaddr_in sin_remote;
+
+	struct bsc_fd bfd;
+	/* linked list of to-be-transmitted msgb's */
+	struct llist_head tx_queue;
+};
+
+struct rtp_socket {
+	struct llist_head list;
+
+	struct rtp_sub_socket rtp;
+	struct rtp_sub_socket rtcp;
+
+	/* what should we do on receive? */
+	enum rtp_rx_action rx_action;
+	union {
+		struct {
+			struct rtp_socket *other_sock;
+		} proxy;
+		struct {
+			struct gsm_network *net;
+			u_int32_t callref;
+		} receive;
+	};
+	enum rtp_tx_action tx_action;
+	struct {
+		u_int16_t sequence;
+		u_int32_t timestamp;
+		u_int32_t ssrc;
+		struct timeval last_tv;
+	} transmit;
+};
+
+struct rtp_socket *rtp_socket_create(void);
+int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip);
+int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port);
+int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other);
+int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net, u_int32_t callref);
+int rtp_socket_free(struct rtp_socket *rs);
+int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame);
+
+#endif /* _RTP_PROXY_H */
diff --git a/include/openbsc/sgsn.h b/include/openbsc/sgsn.h
new file mode 100644
index 0000000..84db87e
--- /dev/null
+++ b/include/openbsc/sgsn.h
@@ -0,0 +1,65 @@
+#ifndef _SGSN_H
+#define _SGSN_H
+
+#include <sys/types.h>
+
+#include <osmocore/msgb.h>
+
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_sgsn.h>
+
+struct sgsn_config {
+	/* parsed from config file */
+
+	char *gtp_statedir;
+	struct sockaddr_in gtp_listenaddr;
+
+	/* misc */
+	struct gprs_ns_inst *nsi;
+};
+
+struct sgsn_instance {
+	char *config_file;
+	struct sgsn_config cfg;
+	/* File descriptor wrappers for LibGTP */
+	struct bsc_fd gtp_fd0;
+	struct bsc_fd gtp_fd1c;
+	struct bsc_fd gtp_fd1u;
+	/* Timer for libGTP */
+	struct timer_list gtp_timer;
+	/* GSN instance for libgtp */
+	struct gsn_t *gsn;
+};
+
+extern struct sgsn_instance *sgsn;
+
+/* sgsn_vty.c */
+
+int sgsn_vty_init(void);
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg);
+
+/* sgsn.c */
+
+/* Main input function for Gb proxy */
+int sgsn_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci);
+
+
+struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn,
+					 struct sgsn_mm_ctx *mmctx,
+					 uint16_t nsapi,
+					 struct tlv_parsed *tp);
+int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx);
+
+/* gprs_sndcp.c */
+
+/* Entry point for the SNSM-ACTIVATE.indication */
+int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi);
+/* Entry point for the SNSM-DEACTIVATE.indication */
+int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi);
+/* Called by SNDCP when it has received/re-assembled a N-PDU */
+int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi,
+			 struct msgb *msg, uint32_t npdu_len, uint8_t *npdu);
+int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi,
+			void *mmcontext);
+
+#endif
diff --git a/include/openbsc/signal.h b/include/openbsc/signal.h
new file mode 100644
index 0000000..a2257db
--- /dev/null
+++ b/include/openbsc/signal.h
@@ -0,0 +1,255 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef OPENBSC_SIGNAL_H
+#define OPENBSC_SIGNAL_H
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <openbsc/gsm_data.h>
+
+#include <osmocore/signal.h>
+
+/*
+ * Signalling subsystems
+ */
+enum signal_subsystems {
+	SS_PAGING,
+	SS_SMS,
+	SS_ABISIP,
+	SS_NM,
+	SS_LCHAN,
+	SS_SUBSCR,
+	SS_SCALL,
+	SS_GLOBAL,
+	SS_CHALLOC,
+	SS_NS,
+	SS_IPAC_NWL,
+	SS_RF,
+	SS_MSC,
+	SS_HO,
+	SS_INPUT,
+};
+
+/* SS_PAGING signals */
+enum signal_paging {
+	S_PAGING_SUCCEEDED,
+	S_PAGING_EXPIRED,
+};
+
+/* SS_SMS signals */
+enum signal_sms {
+	S_SMS_SUBMITTED,	/* A SMS has been successfully submitted to us */
+	S_SMS_DELIVERED,	/* A SMS has been successfully delivered to a MS */
+	S_SMS_SMMA,		/* A MS tells us it has more space available */
+	S_SMS_MEM_EXCEEDED,	/* A MS tells us it has no more space available */
+	S_SMS_UNKNOWN_ERROR,	/* A MS tells us it has an error */
+};
+
+/* SS_ABISIP signals */
+enum signal_abisip {
+	S_ABISIP_CRCX_ACK,
+	S_ABISIP_MDCX_ACK,
+	S_ABISIP_DLCX_IND,
+};
+
+/* SS_NM signals */
+enum signal_nm {
+	S_NM_SW_ACTIV_REP,	/* GSM 12.21 software activated report */
+	S_NM_FAIL_REP,		/* GSM 12.21 failure event report */
+	S_NM_NACK,		/* GSM 12.21 various NM_MT_*_NACK happened */
+	S_NM_IPACC_NACK,	/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_NACK happened */
+	S_NM_IPACC_ACK,		/* GSM 12.21 nanoBTS extensions NM_MT_IPACC_*_*_ACK happened */
+	S_NM_IPACC_RESTART_ACK, /* nanoBTS has send a restart ack */
+	S_NM_IPACC_RESTART_NACK,/* nanoBTS has send a restart ack */
+	S_NM_TEST_REP,		/* GSM 12.21 Test Report */
+	S_NM_STATECHG_OPER,	/* Operational State changed*/
+	S_NM_STATECHG_ADM,	/* Administrative State changed */
+};
+
+/* SS_LCHAN signals */
+enum signal_lchan {
+	/*
+	 * The lchan got freed with an use_count != 0 and error
+	 * recovery needs to be carried out from within the
+	 * signal handler.
+	 */
+	S_LCHAN_UNEXPECTED_RELEASE,
+	S_LCHAN_ACTIVATE_ACK,		/* 08.58 Channel Activate ACK */
+	S_LCHAN_ACTIVATE_NACK,		/* 08.58 Channel Activate NACK */
+	S_LCHAN_HANDOVER_COMPL,		/* 04.08 Handover Completed */
+	S_LCHAN_HANDOVER_FAIL,		/* 04.08 Handover Failed */
+	S_LCHAN_HANDOVER_DETECT,	/* 08.58 Handover Detect */
+	S_LCHAN_MEAS_REP,		/* 08.58 Measurement Report */
+};
+
+/* SS_CHALLOC signals */
+enum signal_challoc {
+	S_CHALLOC_ALLOC_FAIL,	/* allocation of lchan has failed */
+	S_CHALLOC_FREED,	/* lchan has been successfully freed */
+};
+
+/* SS_SUBSCR signals */
+enum signal_subscr {
+	S_SUBSCR_ATTACHED,
+	S_SUBSCR_DETACHED,
+	S_SUBSCR_IDENTITY,		/* we've received some identity information */
+};
+
+/* SS_SCALL signals */
+enum signal_scall {
+	S_SCALL_SUCCESS,
+	S_SCALL_EXPIRED,
+	S_SCALL_DETACHED,
+};
+
+/* SS_IPAC_NWL signals */
+enum signal_ipaccess {
+	S_IPAC_NWL_COMPLETE,
+};
+
+enum signal_global {
+	S_GLOBAL_SHUTDOWN,
+	S_GLOBAL_BTS_CLOSE_OM,
+};
+
+/* SS_RF signals */
+enum signal_rf {
+	S_RF_OFF,
+	S_RF_ON,
+	S_RF_GRACE,
+};
+
+/* SS_INPUT signals */
+enum signal_input {
+	S_INP_NONE,
+	S_INP_TEI_UP,
+	S_INP_TEI_DN,
+	S_INP_LINE_INIT,
+	S_INP_LINE_ALARM,
+	S_INP_LINE_NOALARM,
+};
+
+struct gsm_subscriber;
+
+struct paging_signal_data {
+	struct gsm_subscriber *subscr;
+	struct gsm_bts *bts;
+
+	int paging_result;
+
+	/* NULL in case the paging didn't work */
+	struct gsm_subscriber_connection *conn;
+};
+
+struct scall_signal_data {
+	struct gsm_subscriber *subscr;
+	struct gsm_subscriber_connection *conn;
+	void *data;
+};
+
+struct ipacc_ack_signal_data {
+	struct gsm_bts_trx *trx;
+	u_int8_t msg_type;	
+};
+
+struct nm_statechg_signal_data {
+	u_int8_t obj_class;
+	void *obj;
+	struct gsm_nm_state *old_state;
+	struct gsm_nm_state *new_state;
+	struct abis_om_obj_inst *obj_inst;
+};
+
+struct nm_nack_signal_data {
+	struct msgb *msg;
+	uint8_t mt;
+};
+
+struct challoc_signal_data {
+	struct gsm_bts *bts;
+	struct gsm_lchan *lchan;
+	enum gsm_chan_t type;
+};
+
+struct rf_signal_data {
+	struct gsm_network *net;
+};
+
+struct sms_signal_data {
+	/* The transaction where this occured */
+	struct gsm_trans *trans;
+	/* Can be NULL for SMMA */
+	struct gsm_sms *sms;
+	/* int paging result. Only the ones with > 0 */
+	int paging_result;
+};
+
+struct lchan_signal_data {
+	/* The lchan the signal happened on */
+	struct gsm_lchan *lchan;
+	/* Measurement reports on this lchan */
+	struct gsm_meas_rep *mr;
+};
+
+enum signal_ns {
+	S_NS_RESET,
+	S_NS_BLOCK,
+	S_NS_UNBLOCK,
+	S_NS_ALIVE_EXP,	/* Tns-alive expired more than N times */
+};
+
+struct ns_signal_data {
+	struct gprs_nsvc *nsvc;
+	uint8_t cause;
+};
+
+/* MSC signals */
+enum signal_msc {
+	S_MSC_LOST,
+	S_MSC_CONNECTED,
+};
+
+struct osmo_msc_data;
+struct msc_signal_data {
+	struct osmo_msc_data *data;
+};
+
+/* handover */
+enum signal_ho {
+	S_HANDOVER_ACK,
+};
+
+struct ho_signal_data {
+	struct gsm_lchan *old_lchan;
+	struct gsm_lchan *new_lchan;
+};
+
+struct input_signal_data {
+	int link_type;
+	uint8_t tei;
+	uint8_t sapi;
+	struct gsm_bts_trx *trx;
+	struct e1inp_line *line;
+};
+
+#endif
diff --git a/include/openbsc/silent_call.h b/include/openbsc/silent_call.h
new file mode 100644
index 0000000..2492903
--- /dev/null
+++ b/include/openbsc/silent_call.h
@@ -0,0 +1,12 @@
+#ifndef _SILENT_CALL_H
+#define _SILENT_CALL_H
+
+struct gsm_subscriber_connection;
+
+extern int gsm_silent_call_start(struct gsm_subscriber *subscr,
+                                 void *data, int type);
+extern int gsm_silent_call_stop(struct gsm_subscriber *subscr);
+extern int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg);
+extern int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+#endif /* _SILENT_CALL_H */
diff --git a/include/openbsc/sms_queue.h b/include/openbsc/sms_queue.h
new file mode 100644
index 0000000..2a8bd58
--- /dev/null
+++ b/include/openbsc/sms_queue.h
@@ -0,0 +1,17 @@
+#ifndef SMS_QUEUE_H
+#define SMS_QUEUE_H
+
+struct gsm_network;
+struct gsm_sms_queue;
+struct vty;
+
+int sms_queue_start(struct gsm_network *, int in_flight);
+int sms_queue_trigger(struct gsm_sms_queue *);
+
+/* vty helper functions */
+int sms_queue_stats(struct gsm_sms_queue *, struct vty* vty);
+int sms_queue_set_max_pending(struct gsm_sms_queue *, int max);
+int sms_queue_set_max_failure(struct gsm_sms_queue *, int fail);
+int sms_queue_clear(struct gsm_sms_queue *);
+
+#endif
diff --git a/include/openbsc/socket.h b/include/openbsc/socket.h
new file mode 100644
index 0000000..4d31611
--- /dev/null
+++ b/include/openbsc/socket.h
@@ -0,0 +1,14 @@
+#ifndef _BSC_SOCKET_H
+#define _BSC_SOCKET_H
+
+#include <sys/types.h>
+#include <osmocore/select.h>
+
+#ifndef IPPROTO_GRE
+#define IPPROTO_GRE 47
+#endif
+
+int make_sock(struct bsc_fd *bfd, int proto, u_int32_t ip, u_int16_t port,
+	      int (*cb)(struct bsc_fd *fd, unsigned int what));
+
+#endif /* _BSC_SOCKET_H */
diff --git a/include/openbsc/subchan_demux.h b/include/openbsc/subchan_demux.h
new file mode 100644
index 0000000..da2a7f3
--- /dev/null
+++ b/include/openbsc/subchan_demux.h
@@ -0,0 +1,101 @@
+#ifndef _SUBCH_DEMUX_H
+#define _SUBCH_DEMUX_H
+/* A E1 sub-channel (de)multiplexer with TRAU frame sync */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <osmocore/linuxlist.h>
+
+#define NR_SUBCH	4
+#define TRAU_FRAME_SIZE	40
+#define TRAU_FRAME_BITS	(TRAU_FRAME_SIZE*8)
+
+/***********************************************************************/
+/* DEMULTIPLEXER */
+/***********************************************************************/
+
+struct demux_subch {
+	u_int8_t out_bitbuf[TRAU_FRAME_BITS];
+	u_int16_t out_idx; /* next bit to be written in out_bitbuf */
+	/* number of consecutive zeros that we have received (for sync) */
+	unsigned int consecutive_zeros;
+	/* are we in TRAU frame sync or not? */
+	unsigned int in_sync;
+};
+
+struct subch_demux {
+	/* bitmask of currently active subchannels */
+	u_int8_t chan_activ;
+	/* one demux_subch struct for every subchannel */
+	struct demux_subch subch[NR_SUBCH];
+	/* callback to be called once we have received a complete
+	 * frame on a given subchannel */
+	int (*out_cb)(struct subch_demux *dmx, int ch, u_int8_t *data, int len,
+		      void *);
+	/* user-provided data, transparently passed to out_cb() */
+	void *data;
+};
+
+/* initialize one demultiplexer instance */
+int subch_demux_init(struct subch_demux *dmx);
+
+/* feed 'len' number of muxed bytes into the demultiplexer */
+int subch_demux_in(struct subch_demux *dmx, u_int8_t *data, int len);
+
+/* activate decoding/processing for one subchannel */
+int subch_demux_activate(struct subch_demux *dmx, int subch);
+
+/* deactivate decoding/processing for one subchannel */
+int subch_demux_deactivate(struct subch_demux *dmx, int subch);
+
+/***********************************************************************/
+/* MULTIPLEXER */
+/***********************************************************************/
+
+/* one element in the tx_queue of a muxer sub-channel */
+struct subch_txq_entry {
+	struct llist_head list;
+
+	unsigned int bit_len;	/* total number of bits in 'bits' */
+	unsigned int next_bit;	/* next bit to be transmitted */
+
+	u_int8_t bits[0];	/* one bit per byte */
+};
+
+struct mux_subch {
+	struct llist_head tx_queue;
+};
+
+/* structure representing one instance of the subchannel muxer */
+struct subch_mux {
+	struct mux_subch subch[NR_SUBCH];
+};
+
+/* initialize a subchannel muxer instance */
+int subchan_mux_init(struct subch_mux *mx);
+
+/* request the output of 'len' multiplexed bytes */
+int subchan_mux_out(struct subch_mux *mx, u_int8_t *data, int len);
+
+/* enqueue some data into one sub-channel of the muxer */
+int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const u_int8_t *data,
+			int len);
+
+#endif /* _SUBCH_DEMUX_H */
diff --git a/include/openbsc/system_information.h b/include/openbsc/system_information.h
new file mode 100644
index 0000000..da662e9
--- /dev/null
+++ b/include/openbsc/system_information.h
@@ -0,0 +1,45 @@
+#ifndef _SYSTEM_INFO_H
+#define _SYSTEM_INFO_H
+
+#include <osmocore/utils.h>
+
+#define GSM_MACBLOCK_LEN 		23
+
+struct gsm_bts;
+
+
+enum osmo_sysinfo_type {
+	SYSINFO_TYPE_NONE,
+	SYSINFO_TYPE_1,
+	SYSINFO_TYPE_2,
+	SYSINFO_TYPE_3,
+	SYSINFO_TYPE_4,
+	SYSINFO_TYPE_5,
+	SYSINFO_TYPE_6,
+	SYSINFO_TYPE_7,
+	SYSINFO_TYPE_8,
+	SYSINFO_TYPE_9,
+	SYSINFO_TYPE_10,
+	SYSINFO_TYPE_13,
+	SYSINFO_TYPE_16,
+	SYSINFO_TYPE_17,
+	SYSINFO_TYPE_18,
+	SYSINFO_TYPE_19,
+	SYSINFO_TYPE_20,
+	SYSINFO_TYPE_2bis,
+	SYSINFO_TYPE_2ter,
+	SYSINFO_TYPE_2quater,
+	SYSINFO_TYPE_5bis,
+	SYSINFO_TYPE_5ter,
+	/* FIXME all the various bis and ter */
+	_MAX_SYSINFO_TYPE
+};
+
+typedef u_int8_t sysinfo_buf_t[GSM_MACBLOCK_LEN];
+
+extern const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE];
+uint8_t gsm_sitype2rsl(enum osmo_sysinfo_type si_type);
+const char *gsm_sitype_name(enum osmo_sysinfo_type si_type);
+int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type type);
+
+#endif
diff --git a/include/openbsc/transaction.h b/include/openbsc/transaction.h
new file mode 100644
index 0000000..e41d8ef
--- /dev/null
+++ b/include/openbsc/transaction.h
@@ -0,0 +1,75 @@
+#ifndef _TRANSACT_H
+#define _TRANSACT_H
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_04_11.h>
+
+/* One transaction */
+struct gsm_trans {
+	/* Entry in list of all transactions */
+	struct llist_head entry;
+
+	/* The protocol within which we live */
+	u_int8_t protocol;
+
+	/* The current transaction ID */
+	u_int8_t transaction_id;
+	
+	/* To whom we belong, unique identifier of remote MM entity */
+	struct gsm_subscriber *subscr;
+
+	/* The associated connection we are using to transmit messages */
+	struct gsm_subscriber_connection *conn;
+
+	/* reference from MNCC or other application */
+	u_int32_t callref;
+
+	/* if traffic channel receive was requested */
+	int tch_recv;
+
+	/* is thats one paging? */
+	struct gsm_network **paging_request;
+
+	union {
+		struct {
+
+			/* current call state */
+			int state;
+
+			/* current timer and message queue */
+			int Tcurrent;		/* current CC timer */
+			int T308_second;	/* used to send release again */
+			struct timer_list timer;
+			struct gsm_mncc msg;	/* stores setup/disconnect/release message */
+		} cc;
+		struct {
+			u_int8_t link_id;	/* RSL Link ID to be used for this trans */
+			int is_mt;	/* is this a MO (0) or MT (1) transfer */
+			enum gsm411_cp_state cp_state;
+			struct timer_list cp_timer;
+
+			enum gsm411_rp_state rp_state;
+
+			struct gsm_sms *sms;
+		} sms;
+	};
+};
+
+
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr,
+				   u_int8_t proto, u_int8_t trans_id);
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					u_int32_t callref);
+
+struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr,
+			      u_int8_t protocol, u_int8_t trans_id,
+			      u_int32_t callref);
+void trans_free(struct gsm_trans *trans);
+
+int trans_assign_trans_id(struct gsm_subscriber *subscr,
+			  u_int8_t protocol, u_int8_t ti_flag);
+
+#endif
diff --git a/include/openbsc/trau_frame.h b/include/openbsc/trau_frame.h
new file mode 100644
index 0000000..c594c38
--- /dev/null
+++ b/include/openbsc/trau_frame.h
@@ -0,0 +1,64 @@
+#ifndef _TRAU_FRAME_H
+#define _TRAU_FRAME_H
+/* TRAU frame handling according to GSM TS 08.60 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+/* 21 for FR/EFR, 25 for AMR, 15 for OM, 15 for data, 13 for E-data, 21 idle */
+#define MAX_C_BITS	25
+/* 260 for FR/EFR, 256 for AMR, 264 for OM, 288 for E-data */
+#define MAX_D_BITS	288
+/* for all speech frames */
+#define MAX_T_BITS	4
+/* for OM */
+#define MAX_S_BITS	6
+/* for E-data */
+#define MAX_M_BITS	2
+
+struct decoded_trau_frame {
+	u_int8_t c_bits[MAX_C_BITS];
+	u_int8_t d_bits[MAX_D_BITS];
+	u_int8_t t_bits[MAX_T_BITS];
+	u_int8_t s_bits[MAX_S_BITS];
+	u_int8_t m_bits[MAX_M_BITS];
+};
+
+#define TRAU_FT_FR_UP		0x02	/* 0 0 0 1 0 - 3.5.1.1.1 */
+#define TRAU_FT_FR_DOWN		0x1c	/* 1 1 1 0 0 - 3.5.1.1.1 */
+#define TRAU_FT_EFR		0x1a	/* 1 1 0 1 0 - 3.5.1.1.1 */
+#define TRAU_FT_AMR		0x06	/* 0 0 1 1 0 - 3.5.1.2 */
+#define TRAU_FT_OM_UP		0x07	/* 0 0 1 0 1 - 3.5.2 */
+#define TRAU_FT_OM_DOWN		0x1b	/* 1 1 0 1 1 - 3.5.2 */
+#define TRAU_FT_DATA_UP		0x08	/* 0 1 0 0 0 - 3.5.3 */
+#define TRAU_FT_DATA_DOWN	0x16	/* 1 0 1 1 0 - 3.5.3 */
+#define TRAU_FT_D145_SYNC	0x14	/* 1 0 1 0 0 - 3.5.3 */
+#define TRAU_FT_EDATA		0x1f	/* 1 1 1 1 1 - 3.5.4 */
+#define TRAU_FT_IDLE_UP		0x10	/* 1 0 0 0 0 - 3.5.5 */
+#define TRAU_FT_IDLE_DOWN	0x0e	/* 0 1 1 1 0 - 3.5.5 */
+
+
+int decode_trau_frame(struct decoded_trau_frame *fr, const u_int8_t *trau_bits);
+int encode_trau_frame(u_int8_t *trau_bits, const struct decoded_trau_frame *fr);
+int trau_frame_up2down(struct decoded_trau_frame *fr);
+u_int8_t *trau_idle_frame(void);
+
+
+#endif /* _TRAU_FRAME_H */
diff --git a/include/openbsc/trau_mux.h b/include/openbsc/trau_mux.h
new file mode 100644
index 0000000..dcf33ee
--- /dev/null
+++ b/include/openbsc/trau_mux.h
@@ -0,0 +1,48 @@
+/* Simple TRAU frame reflector to route voice calls */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* The "TRAU mux map" defines which particular 16kbit sub-slot (in which E1
+ * timeslot on which E1 interface) should be directly muxed to which other 
+ * sub-slot.  Entries in the mux map are always bi-directional. 
+ *
+ * The idea of all this is to directly switch voice channels in the BSC
+ * from one phone to another.  We do this right now since we don't support
+ * any external interface for voice channels, and in the future as an
+ * optimization to routing them externally.
+ */
+
+/* map a TRAU mux map entry */
+int trau_mux_map(const struct gsm_e1_subslot *src,
+		 const struct gsm_e1_subslot *dst);
+int trau_mux_map_lchan(const struct gsm_lchan *src,	
+			const struct gsm_lchan *dst);
+
+/* unmap a TRAU mux map entry */
+int trau_mux_unmap(const struct gsm_e1_subslot *ss, u_int32_t callref);
+
+/* we get called by subchan_demux */
+int trau_mux_input(struct gsm_e1_subslot *src_e1_ss,
+		   const u_int8_t *trau_bits, int num_bits);
+
+/* add a trau receiver */
+int trau_recv_lchan(struct gsm_lchan *lchan, u_int32_t callref);
+
+/* send trau from application */
+int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame);
diff --git a/include/openbsc/ussd.h b/include/openbsc/ussd.h
new file mode 100644
index 0000000..6f80d23
--- /dev/null
+++ b/include/openbsc/ussd.h
@@ -0,0 +1,10 @@
+#ifndef _USSD_H
+#define _USSD_H
+
+/* Handler function for mobile-originated USSD messages */
+
+#include <osmocore/msgb.h>
+
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg);
+
+#endif
diff --git a/include/openbsc/vty.h b/include/openbsc/vty.h
new file mode 100644
index 0000000..516c8c2
--- /dev/null
+++ b/include/openbsc/vty.h
@@ -0,0 +1,46 @@
+#ifndef OPENBSC_VTY_H
+#define OPENBSC_VTY_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/command.h>
+
+struct gsm_network;
+struct vty;
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *);
+
+struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base);
+
+extern struct cmd_element cfg_description_cmd;
+extern struct cmd_element cfg_no_description_cmd;
+extern struct cmd_element ournode_exit_cmd;
+extern struct cmd_element ournode_end_cmd;
+
+enum bsc_vty_node {
+	GSMNET_NODE = _LAST_OSMOVTY_NODE + 1,
+	BTS_NODE,
+	TRX_NODE,
+	TS_NODE,
+	SUBSCR_NODE,
+	MGCP_NODE,
+	GBPROXY_NODE,
+	SGSN_NODE,
+	NS_NODE,
+	BSSGP_NODE,
+	OML_NODE,
+	E1INP_NODE,
+	NAT_NODE,
+	NAT_BSC_NODE,
+	MSC_NODE,
+	OM2K_NODE,
+	TRUNK_NODE,
+};
+
+extern int bsc_vty_is_config_node(struct vty *vty, int node);
+extern void bsc_replace_string(void *ctx, char **dst, const char *newstr);
+
+int bsc_vty_init(void);
+int bsc_vty_init_extra(void);
+
+#endif
diff --git a/install-sh b/install-sh
new file mode 100755
index 0000000..6781b98
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,520 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2009-04-28.21; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" ""	$nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit=${DOITPROG-}
+if test -z "$doit"; then
+  doit_exec=exec
+else
+  doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_glob='?'
+initialize_posix_glob='
+  test "$posix_glob" != "?" || {
+    if (set -f) 2>/dev/null; then
+      posix_glob=
+    else
+      posix_glob=:
+    fi
+  }
+'
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+no_target_directory=
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
+
+  -c            (ignored)
+  -C            install only if different (preserve the last data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -s            $stripprog installed files.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
+
+    -C) copy_on_change=true;;
+
+    -d) dir_arg=true;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+	shift;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+	case $mode in
+	  *' '* | *'	'* | *'
+'*	  | *'*'* | *'?'* | *'['*)
+	    echo "$0: invalid mode: $mode" >&2
+	    exit 1;;
+	esac
+	shift;;
+
+    -o) chowncmd="$chownprog $2"
+	shift;;
+
+    -s) stripcmd=$stripprog;;
+
+    -t) dst_arg=$2
+	shift;;
+
+    -T) no_target_directory=true;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --)	shift
+	break;;
+
+    -*)	echo "$0: invalid option: $1" >&2
+	exit 1;;
+
+    *)  break;;
+  esac
+  shift
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call `install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  trap '(exit $?); exit' 1 2 13 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names starting with `-'.
+  case $src in
+    -*) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+
+    dst=$dst_arg
+    # Protect names starting with `-'.
+    case $dst in
+      -*) dst=./$dst;;
+    esac
+
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test -n "$no_target_directory"; then
+	echo "$0: $dst_arg: Is a directory" >&2
+	exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      # Prefer dirname, but fall back on a substitute if dirname fails.
+      dstdir=`
+	(dirname "$dst") 2>/dev/null ||
+	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	     X"$dst" : 'X\(//\)[^/]' \| \
+	     X"$dst" : 'X\(//\)$' \| \
+	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+	echo X"$dst" |
+	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)[^/].*/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\).*/{
+		   s//\1/
+		   q
+		 }
+		 s/.*/./; q'
+      `
+
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+	# Create intermediate dirs using mode 755 as modified by the umask.
+	# This is like FreeBSD 'install' as of 1997-10-28.
+	umask=`umask`
+	case $stripcmd.$umask in
+	  # Optimize common cases.
+	  *[2367][2367]) mkdir_umask=$umask;;
+	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+	  *[0-7])
+	    mkdir_umask=`expr $umask + 22 \
+	      - $umask % 100 % 40 + $umask % 20 \
+	      - $umask % 10 % 4 + $umask % 2
+	    `;;
+	  *) mkdir_umask=$umask,go-w;;
+	esac
+
+	# With -d, create the new directory with the user-specified mode.
+	# Otherwise, rely on $mkdir_umask.
+	if test -n "$dir_arg"; then
+	  mkdir_mode=-m$mode
+	else
+	  mkdir_mode=
+	fi
+
+	posix_mkdir=false
+	case $umask in
+	  *[123567][0-7][0-7])
+	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
+	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+	    ;;
+	  *)
+	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+	    if (umask $mkdir_umask &&
+		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+	    then
+	      if test -z "$dir_arg" || {
+		   # Check for POSIX incompatibilities with -m.
+		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+		   # other-writeable bit of parent directory when it shouldn't.
+		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
+		   case $ls_ld_tmpdir in
+		     d????-?r-*) different_mode=700;;
+		     d????-?--*) different_mode=755;;
+		     *) false;;
+		   esac &&
+		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+		   }
+		 }
+	      then posix_mkdir=:
+	      fi
+	      rmdir "$tmpdir/d" "$tmpdir"
+	    else
+	      # Remove any dirs left behind by ancient mkdir implementations.
+	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+	    fi
+	    trap '' 0;;
+	esac;;
+    esac
+
+    if
+      $posix_mkdir && (
+	umask $mkdir_umask &&
+	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+	/*) prefix='/';;
+	-*) prefix='./';;
+	*)  prefix='';;
+      esac
+
+      eval "$initialize_posix_glob"
+
+      oIFS=$IFS
+      IFS=/
+      $posix_glob set -f
+      set fnord $dstdir
+      shift
+      $posix_glob set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+	test -z "$d" && continue
+
+	prefix=$prefix$d
+	if test -d "$prefix"; then
+	  prefixes=
+	else
+	  if $posix_mkdir; then
+	    (umask=$mkdir_umask &&
+	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+	    # Don't fail if two instances are running concurrently.
+	    test -d "$prefix" || exit 1
+	  else
+	    case $prefix in
+	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+	      *) qprefix=$prefix;;
+	    esac
+	    prefixes="$prefixes '$qprefix'"
+	  fi
+	fi
+	prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+	# Don't fail if two instances are running concurrently.
+	(umask $mkdir_umask &&
+	 eval "\$doit_exec \$mkdirprog $prefixes") ||
+	  test -d "$dstdir" || exit 1
+	obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&
+
+       eval "$initialize_posix_glob" &&
+       $posix_glob set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       $posix_glob set +f &&
+
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+	# Now remove or move aside any old file at destination location.
+	# We try this two ways since rm can't unlink itself on some
+	# systems and the destination file might be busy for other
+	# reasons.  In this case, the final cleanup might fail but the new
+	# file should still install successfully.
+	{
+	  test ! -f "$dst" ||
+	  $doit $rmcmd -f "$dst" 2>/dev/null ||
+	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+	  } ||
+	  { echo "$0: cannot unlink or rename $dst" >&2
+	    (exit 1); exit 1
+	  }
+	} &&
+
+	# Now rename the file to the real destination.
+	$doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/missing b/missing
new file mode 100755
index 0000000..28055d2
--- /dev/null
+++ b/missing
@@ -0,0 +1,376 @@
+#! /bin/sh
+# Common stub for a few missing GNU programs while installing.
+
+scriptversion=2009-04-28.21; # UTC
+
+# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006,
+# 2008, 2009 Free Software Foundation, Inc.
+# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+if test $# -eq 0; then
+  echo 1>&2 "Try \`$0 --help' for more information"
+  exit 1
+fi
+
+run=:
+sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p'
+sed_minuso='s/.* -o \([^ ]*\).*/\1/p'
+
+# In the cases where this matters, `missing' is being run in the
+# srcdir already.
+if test -f configure.ac; then
+  configure_ac=configure.ac
+else
+  configure_ac=configure.in
+fi
+
+msg="missing on your system"
+
+case $1 in
+--run)
+  # Try to run requested program, and just exit if it succeeds.
+  run=
+  shift
+  "$@" && exit 0
+  # Exit code 63 means version mismatch.  This often happens
+  # when the user try to use an ancient version of a tool on
+  # a file that requires a minimum version.  In this case we
+  # we should proceed has if the program had been absent, or
+  # if --run hadn't been passed.
+  if test $? = 63; then
+    run=:
+    msg="probably too old"
+  fi
+  ;;
+
+  -h|--h|--he|--hel|--help)
+    echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
+error status if there is no known handling for PROGRAM.
+
+Options:
+  -h, --help      display this help and exit
+  -v, --version   output version information and exit
+  --run           try to run the given command, and emulate it if it fails
+
+Supported PROGRAM values:
+  aclocal      touch file \`aclocal.m4'
+  autoconf     touch file \`configure'
+  autoheader   touch file \`config.h.in'
+  autom4te     touch the output file, or create a stub one
+  automake     touch all \`Makefile.in' files
+  bison        create \`y.tab.[ch]', if possible, from existing .[ch]
+  flex         create \`lex.yy.c', if possible, from existing .c
+  help2man     touch the output file
+  lex          create \`lex.yy.c', if possible, from existing .c
+  makeinfo     touch the output file
+  tar          try tar, gnutar, gtar, then tar without non-portable flags
+  yacc         create \`y.tab.[ch]', if possible, from existing .[ch]
+
+Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and
+\`g' are ignored when checking the name.
+
+Send bug reports to <bug-automake@gnu.org>."
+    exit $?
+    ;;
+
+  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+    echo "missing $scriptversion (GNU Automake)"
+    exit $?
+    ;;
+
+  -*)
+    echo 1>&2 "$0: Unknown \`$1' option"
+    echo 1>&2 "Try \`$0 --help' for more information"
+    exit 1
+    ;;
+
+esac
+
+# normalize program name to check for.
+program=`echo "$1" | sed '
+  s/^gnu-//; t
+  s/^gnu//; t
+  s/^g//; t'`
+
+# Now exit if we have it, but it failed.  Also exit now if we
+# don't have it and --version was passed (most likely to detect
+# the program).  This is about non-GNU programs, so use $1 not
+# $program.
+case $1 in
+  lex*|yacc*)
+    # Not GNU programs, they don't have --version.
+    ;;
+
+  tar*)
+    if test -n "$run"; then
+       echo 1>&2 "ERROR: \`tar' requires --run"
+       exit 1
+    elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
+       exit 1
+    fi
+    ;;
+
+  *)
+    if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
+       # We have it, but it failed.
+       exit 1
+    elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
+       # Could not run --version or --help.  This is probably someone
+       # running `$TOOL --version' or `$TOOL --help' to check whether
+       # $TOOL exists and not knowing $TOOL uses missing.
+       exit 1
+    fi
+    ;;
+esac
+
+# If it does not exist, or fails to run (possibly an outdated version),
+# try to emulate it.
+case $program in
+  aclocal*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`acinclude.m4' or \`${configure_ac}'.  You might want
+         to install the \`Automake' and \`Perl' packages.  Grab them from
+         any GNU archive site."
+    touch aclocal.m4
+    ;;
+
+  autoconf*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`${configure_ac}'.  You might want to install the
+         \`Autoconf' and \`GNU m4' packages.  Grab them from any GNU
+         archive site."
+    touch configure
+    ;;
+
+  autoheader*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`acconfig.h' or \`${configure_ac}'.  You might want
+         to install the \`Autoconf' and \`GNU m4' packages.  Grab them
+         from any GNU archive site."
+    files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
+    test -z "$files" && files="config.h"
+    touch_files=
+    for f in $files; do
+      case $f in
+      *:*) touch_files="$touch_files "`echo "$f" |
+				       sed -e 's/^[^:]*://' -e 's/:.*//'`;;
+      *) touch_files="$touch_files $f.in";;
+      esac
+    done
+    touch $touch_files
+    ;;
+
+  automake*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
+         You might want to install the \`Automake' and \`Perl' packages.
+         Grab them from any GNU archive site."
+    find . -type f -name Makefile.am -print |
+	   sed 's/\.am$/.in/' |
+	   while read f; do touch "$f"; done
+    ;;
+
+  autom4te*)
+    echo 1>&2 "\
+WARNING: \`$1' is needed, but is $msg.
+         You might have modified some files without having the
+         proper tools for further handling them.
+         You can get \`$1' as part of \`Autoconf' from any GNU
+         archive site."
+
+    file=`echo "$*" | sed -n "$sed_output"`
+    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    if test -f "$file"; then
+	touch $file
+    else
+	test -z "$file" || exec >$file
+	echo "#! /bin/sh"
+	echo "# Created by GNU Automake missing as a replacement of"
+	echo "#  $ $@"
+	echo "exit 0"
+	chmod +x $file
+	exit 1
+    fi
+    ;;
+
+  bison*|yacc*)
+    echo 1>&2 "\
+WARNING: \`$1' $msg.  You should only need it if
+         you modified a \`.y' file.  You may need the \`Bison' package
+         in order for those modifications to take effect.  You can get
+         \`Bison' from any GNU archive site."
+    rm -f y.tab.c y.tab.h
+    if test $# -ne 1; then
+        eval LASTARG="\${$#}"
+	case $LASTARG in
+	*.y)
+	    SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
+	    if test -f "$SRCFILE"; then
+	         cp "$SRCFILE" y.tab.c
+	    fi
+	    SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
+	    if test -f "$SRCFILE"; then
+	         cp "$SRCFILE" y.tab.h
+	    fi
+	  ;;
+	esac
+    fi
+    if test ! -f y.tab.h; then
+	echo >y.tab.h
+    fi
+    if test ! -f y.tab.c; then
+	echo 'main() { return 0; }' >y.tab.c
+    fi
+    ;;
+
+  lex*|flex*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified a \`.l' file.  You may need the \`Flex' package
+         in order for those modifications to take effect.  You can get
+         \`Flex' from any GNU archive site."
+    rm -f lex.yy.c
+    if test $# -ne 1; then
+        eval LASTARG="\${$#}"
+	case $LASTARG in
+	*.l)
+	    SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
+	    if test -f "$SRCFILE"; then
+	         cp "$SRCFILE" lex.yy.c
+	    fi
+	  ;;
+	esac
+    fi
+    if test ! -f lex.yy.c; then
+	echo 'main() { return 0; }' >lex.yy.c
+    fi
+    ;;
+
+  help2man*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+	 you modified a dependency of a manual page.  You may need the
+	 \`Help2man' package in order for those modifications to take
+	 effect.  You can get \`Help2man' from any GNU archive site."
+
+    file=`echo "$*" | sed -n "$sed_output"`
+    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    if test -f "$file"; then
+	touch $file
+    else
+	test -z "$file" || exec >$file
+	echo ".ab help2man is required to generate this page"
+	exit $?
+    fi
+    ;;
+
+  makeinfo*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified a \`.texi' or \`.texinfo' file, or any other file
+         indirectly affecting the aspect of the manual.  The spurious
+         call might also be the consequence of using a buggy \`make' (AIX,
+         DU, IRIX).  You might want to install the \`Texinfo' package or
+         the \`GNU make' package.  Grab either from any GNU archive site."
+    # The file to touch is that specified with -o ...
+    file=`echo "$*" | sed -n "$sed_output"`
+    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    if test -z "$file"; then
+      # ... or it is the one specified with @setfilename ...
+      infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
+      file=`sed -n '
+	/^@setfilename/{
+	  s/.* \([^ ]*\) *$/\1/
+	  p
+	  q
+	}' $infile`
+      # ... or it is derived from the source name (dir/f.texi becomes f.info)
+      test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info
+    fi
+    # If the file does not exist, the user really needs makeinfo;
+    # let's fail without touching anything.
+    test -f $file || exit 1
+    touch $file
+    ;;
+
+  tar*)
+    shift
+
+    # We have already tried tar in the generic part.
+    # Look for gnutar/gtar before invocation to avoid ugly error
+    # messages.
+    if (gnutar --version > /dev/null 2>&1); then
+       gnutar "$@" && exit 0
+    fi
+    if (gtar --version > /dev/null 2>&1); then
+       gtar "$@" && exit 0
+    fi
+    firstarg="$1"
+    if shift; then
+	case $firstarg in
+	*o*)
+	    firstarg=`echo "$firstarg" | sed s/o//`
+	    tar "$firstarg" "$@" && exit 0
+	    ;;
+	esac
+	case $firstarg in
+	*h*)
+	    firstarg=`echo "$firstarg" | sed s/h//`
+	    tar "$firstarg" "$@" && exit 0
+	    ;;
+	esac
+    fi
+
+    echo 1>&2 "\
+WARNING: I can't seem to be able to run \`tar' with the given arguments.
+         You may want to install GNU tar or Free paxutils, or check the
+         command line arguments."
+    exit 1
+    ;;
+
+  *)
+    echo 1>&2 "\
+WARNING: \`$1' is needed, and is $msg.
+         You might have modified some files without having the
+         proper tools for further handling them.  Check the \`README' file,
+         it often tells you about the needed prerequisites for installing
+         this package.  You may also peek at any GNU archive site, in case
+         some other package would contain this missing \`$1' program."
+    exit 1
+    ;;
+esac
+
+exit 0
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/openbsc.pc.in b/openbsc.pc.in
new file mode 100644
index 0000000..aba07e2
--- /dev/null
+++ b/openbsc.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/
+
+Name: OpenBSC
+Description: OpenBSC base station controller
+Requires:
+Version: @VERSION@
+Libs: -L${libdir} -lopenbsc
+Cflags: -I${includedir}
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..1573563
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs
+
+# Conditional modules
+if BUILD_NAT
+SUBDIRS += osmo-bsc_nat
+endif
+if BUILD_BSC
+SUBDIRS += osmo-bsc
+endif
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..b1c2f08
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,548 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+
+# Conditional modules
+@BUILD_NAT_TRUE@am__append_1 = osmo-bsc_nat
+@BUILD_BSC_TRUE@am__append_2 = osmo-bsc
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+	html-recursive info-recursive install-data-recursive \
+	install-dvi-recursive install-exec-recursive \
+	install-html-recursive install-info-recursive \
+	install-pdf-recursive install-ps-recursive install-recursive \
+	installcheck-recursive installdirs-recursive pdf-recursive \
+	ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
+  distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+	$(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+	distdir
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau \
+	osmo-nitb osmo-bsc_mgcp utils ipaccess libgb gprs osmo-bsc_nat \
+	osmo-bsc
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+SUBDIRS = libcommon libabis libmgcp libbsc libmsc libtrau osmo-nitb \
+	osmo-bsc_mgcp utils ipaccess libgb gprs $(am__append_1) \
+	$(am__append_2)
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+#     (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	target=`echo $@ | sed s/-recursive//`; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    dot_seen=yes; \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done; \
+	if test "$$dot_seen" = "no"; then \
+	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+	fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	case "$@" in \
+	  distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+	  *) list='$(SUBDIRS)' ;; \
+	esac; \
+	rev=''; for subdir in $$list; do \
+	  if test "$$subdir" = "."; then :; else \
+	    rev="$$subdir $$rev"; \
+	  fi; \
+	done; \
+	rev="$$rev ."; \
+	target=`echo $@ | sed s/-recursive//`; \
+	for subdir in $$rev; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done && test -z "$$fail"
+tags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+	done
+ctags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+	done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+	  include_option=--etags-include; \
+	  empty_fix=.; \
+	else \
+	  include_option=--include; \
+	  empty_fix=; \
+	fi; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test ! -f $$subdir/TAGS || \
+	      set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+	  fi; \
+	done; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test -d "$(distdir)/$$subdir" \
+	    || $(MKDIR_P) "$(distdir)/$$subdir" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+	    $(am__relativize); \
+	    new_distdir=$$reldir; \
+	    dir1=$$subdir; dir2="$(top_distdir)"; \
+	    $(am__relativize); \
+	    new_top_distdir=$$reldir; \
+	    echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+	    echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+	    ($(am__cd) $$subdir && \
+	      $(MAKE) $(AM_MAKEFLAGS) \
+	        top_distdir="$$new_top_distdir" \
+	        distdir="$$new_distdir" \
+		am__remove_distdir=: \
+		am__skip_length_check=: \
+		am__skip_mode_fix=: \
+	        distdir) \
+	      || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \
+	install-am install-strip tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+	all all-am check check-am clean clean-generic ctags \
+	ctags-recursive distclean distclean-generic distclean-tags \
+	distdir dvi dvi-am html html-am info info-am install \
+	install-am install-data install-data-am install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am install-man \
+	install-pdf install-pdf-am install-ps install-ps-am \
+	install-strip installcheck installcheck-am installdirs \
+	installdirs-am maintainer-clean maintainer-clean-generic \
+	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
+	tags-recursive uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/gprs/Makefile.am b/src/gprs/Makefile.am
new file mode 100644
index 0000000..16c2200
--- /dev/null
+++ b/src/gprs/Makefile.am
@@ -0,0 +1,22 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_HEADERS = gprs_sndcp.h
+
+if HAVE_LIBGTP
+bin_PROGRAMS = osmo-gbproxy osmo-sgsn
+else
+bin_PROGRAMS = osmo-gbproxy
+endif
+
+osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c
+osmo_gbproxy_LDADD = 	$(top_builddir)/src/libgb/libgb.a \
+			$(top_builddir)/src/libcommon/libcommon.a
+
+osmo_sgsn_SOURCES =	gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \
+			sgsn_main.c sgsn_vty.c sgsn_libgtp.c \
+			gprs_llc.c gprs_llc_vty.c crc24.c
+osmo_sgsn_LDADD = 	$(top_builddir)/src/libgb/libgb.a \
+			$(top_builddir)/src/libcommon/libcommon.a \
+			-lgtp
diff --git a/src/gprs/Makefile.in b/src/gprs/Makefile.in
new file mode 100644
index 0000000..99ea739
--- /dev/null
+++ b/src/gprs/Makefile.in
@@ -0,0 +1,522 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+@HAVE_LIBGTP_FALSE@bin_PROGRAMS = osmo-gbproxy$(EXEEXT)
+@HAVE_LIBGTP_TRUE@bin_PROGRAMS = osmo-gbproxy$(EXEEXT) \
+@HAVE_LIBGTP_TRUE@	osmo-sgsn$(EXEEXT)
+subdir = src/gprs
+DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+	$(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_osmo_gbproxy_OBJECTS = gb_proxy.$(OBJEXT) gb_proxy_main.$(OBJEXT) \
+	gb_proxy_vty.$(OBJEXT)
+osmo_gbproxy_OBJECTS = $(am_osmo_gbproxy_OBJECTS)
+osmo_gbproxy_DEPENDENCIES = $(top_builddir)/src/libgb/libgb.a \
+	$(top_builddir)/src/libcommon/libcommon.a
+am_osmo_sgsn_OBJECTS = gprs_gmm.$(OBJEXT) gprs_sgsn.$(OBJEXT) \
+	gprs_sndcp.$(OBJEXT) gprs_sndcp_vty.$(OBJEXT) \
+	sgsn_main.$(OBJEXT) sgsn_vty.$(OBJEXT) sgsn_libgtp.$(OBJEXT) \
+	gprs_llc.$(OBJEXT) gprs_llc_vty.$(OBJEXT) crc24.$(OBJEXT)
+osmo_sgsn_OBJECTS = $(am_osmo_sgsn_OBJECTS)
+osmo_sgsn_DEPENDENCIES = $(top_builddir)/src/libgb/libgb.a \
+	$(top_builddir)/src/libcommon/libcommon.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(osmo_gbproxy_SOURCES) $(osmo_sgsn_SOURCES)
+DIST_SOURCES = $(osmo_gbproxy_SOURCES) $(osmo_sgsn_SOURCES)
+HEADERS = $(noinst_HEADERS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+noinst_HEADERS = gprs_sndcp.h
+osmo_gbproxy_SOURCES = gb_proxy.c gb_proxy_main.c gb_proxy_vty.c
+osmo_gbproxy_LDADD = $(top_builddir)/src/libgb/libgb.a \
+			$(top_builddir)/src/libcommon/libcommon.a
+
+osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \
+			sgsn_main.c sgsn_vty.c sgsn_libgtp.c \
+			gprs_llc.c gprs_llc_vty.c crc24.c
+
+osmo_sgsn_LDADD = $(top_builddir)/src/libgb/libgb.a \
+			$(top_builddir)/src/libcommon/libcommon.a \
+			-lgtp
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/gprs/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/gprs/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+osmo-gbproxy$(EXEEXT): $(osmo_gbproxy_OBJECTS) $(osmo_gbproxy_DEPENDENCIES) 
+	@rm -f osmo-gbproxy$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(osmo_gbproxy_OBJECTS) $(osmo_gbproxy_LDADD) $(LIBS)
+osmo-sgsn$(EXEEXT): $(osmo_sgsn_OBJECTS) $(osmo_sgsn_DEPENDENCIES) 
+	@rm -f osmo-sgsn$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(osmo_sgsn_OBJECTS) $(osmo_sgsn_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc24.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy_main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gb_proxy_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_gmm.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_llc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_llc_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sgsn.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sndcp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_sndcp_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_libgtp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgsn_vty.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/gprs/crc24.c b/src/gprs/crc24.c
new file mode 100644
index 0000000..4d65e6e
--- /dev/null
+++ b/src/gprs/crc24.c
@@ -0,0 +1,68 @@
+/* GPRS LLC CRC-24 Implementation */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <openbsc/crc24.h>
+
+/* CRC24 table - FCS */
+static const u_int32_t tbl_crc24[256] = {
+	0x00000000, 0x00d6a776, 0x00f64557, 0x0020e221, 0x00b78115, 0x00612663, 0x0041c442, 0x00976334,
+	0x00340991, 0x00e2aee7, 0x00c24cc6, 0x0014ebb0, 0x00838884, 0x00552ff2, 0x0075cdd3, 0x00a36aa5,
+	0x00681322, 0x00beb454, 0x009e5675, 0x0048f103, 0x00df9237, 0x00093541, 0x0029d760, 0x00ff7016,
+	0x005c1ab3, 0x008abdc5, 0x00aa5fe4, 0x007cf892, 0x00eb9ba6, 0x003d3cd0, 0x001ddef1, 0x00cb7987,
+	0x00d02644, 0x00068132, 0x00266313, 0x00f0c465, 0x0067a751, 0x00b10027, 0x0091e206, 0x00474570,
+	0x00e42fd5, 0x003288a3, 0x00126a82, 0x00c4cdf4, 0x0053aec0, 0x008509b6, 0x00a5eb97, 0x00734ce1,
+	0x00b83566, 0x006e9210, 0x004e7031, 0x0098d747, 0x000fb473, 0x00d91305, 0x00f9f124, 0x002f5652,
+	0x008c3cf7, 0x005a9b81, 0x007a79a0, 0x00acded6, 0x003bbde2, 0x00ed1a94, 0x00cdf8b5, 0x001b5fc3,
+	0x00fb4733, 0x002de045, 0x000d0264, 0x00dba512, 0x004cc626, 0x009a6150, 0x00ba8371, 0x006c2407,
+	0x00cf4ea2, 0x0019e9d4, 0x00390bf5, 0x00efac83, 0x0078cfb7, 0x00ae68c1, 0x008e8ae0, 0x00582d96,
+	0x00935411, 0x0045f367, 0x00651146, 0x00b3b630, 0x0024d504, 0x00f27272, 0x00d29053, 0x00043725,
+	0x00a75d80, 0x0071faf6, 0x005118d7, 0x0087bfa1, 0x0010dc95, 0x00c67be3, 0x00e699c2, 0x00303eb4,
+	0x002b6177, 0x00fdc601, 0x00dd2420, 0x000b8356, 0x009ce062, 0x004a4714, 0x006aa535, 0x00bc0243,
+	0x001f68e6, 0x00c9cf90, 0x00e92db1, 0x003f8ac7, 0x00a8e9f3, 0x007e4e85, 0x005eaca4, 0x00880bd2,
+	0x00437255, 0x0095d523, 0x00b53702, 0x00639074, 0x00f4f340, 0x00225436, 0x0002b617, 0x00d41161,
+	0x00777bc4, 0x00a1dcb2, 0x00813e93, 0x005799e5, 0x00c0fad1, 0x00165da7, 0x0036bf86, 0x00e018f0,
+	0x00ad85dd, 0x007b22ab, 0x005bc08a, 0x008d67fc, 0x001a04c8, 0x00cca3be, 0x00ec419f, 0x003ae6e9,
+	0x00998c4c, 0x004f2b3a, 0x006fc91b, 0x00b96e6d, 0x002e0d59, 0x00f8aa2f, 0x00d8480e, 0x000eef78,
+	0x00c596ff, 0x00133189, 0x0033d3a8, 0x00e574de, 0x007217ea, 0x00a4b09c, 0x008452bd, 0x0052f5cb,
+	0x00f19f6e, 0x00273818, 0x0007da39, 0x00d17d4f, 0x00461e7b, 0x0090b90d, 0x00b05b2c, 0x0066fc5a,
+	0x007da399, 0x00ab04ef, 0x008be6ce, 0x005d41b8, 0x00ca228c, 0x001c85fa, 0x003c67db, 0x00eac0ad,
+	0x0049aa08, 0x009f0d7e, 0x00bfef5f, 0x00694829, 0x00fe2b1d, 0x00288c6b, 0x00086e4a, 0x00dec93c,
+	0x0015b0bb, 0x00c317cd, 0x00e3f5ec, 0x0035529a, 0x00a231ae, 0x007496d8, 0x005474f9, 0x0082d38f,
+	0x0021b92a, 0x00f71e5c, 0x00d7fc7d, 0x00015b0b, 0x0096383f, 0x00409f49, 0x00607d68, 0x00b6da1e,
+	0x0056c2ee, 0x00806598, 0x00a087b9, 0x007620cf, 0x00e143fb, 0x0037e48d, 0x001706ac, 0x00c1a1da,
+	0x0062cb7f, 0x00b46c09, 0x00948e28, 0x0042295e, 0x00d54a6a, 0x0003ed1c, 0x00230f3d, 0x00f5a84b,
+	0x003ed1cc, 0x00e876ba, 0x00c8949b, 0x001e33ed, 0x008950d9, 0x005ff7af, 0x007f158e, 0x00a9b2f8,
+	0x000ad85d, 0x00dc7f2b, 0x00fc9d0a, 0x002a3a7c, 0x00bd5948, 0x006bfe3e, 0x004b1c1f, 0x009dbb69,
+	0x0086e4aa, 0x005043dc, 0x0070a1fd, 0x00a6068b, 0x003165bf, 0x00e7c2c9, 0x00c720e8, 0x0011879e,
+	0x00b2ed3b, 0x00644a4d, 0x0044a86c, 0x00920f1a, 0x00056c2e, 0x00d3cb58, 0x00f32979, 0x00258e0f,
+	0x00eef788, 0x003850fe, 0x0018b2df, 0x00ce15a9, 0x0059769d, 0x008fd1eb, 0x00af33ca, 0x007994bc,
+	0x00dafe19, 0x000c596f, 0x002cbb4e, 0x00fa1c38, 0x006d7f0c, 0x00bbd87a, 0x009b3a5b, 0x004d9d2d
+};
+
+#define INIT_CRC24	0xffffff
+
+u_int32_t crc24_calc(u_int32_t fcs, u_int8_t *cp, unsigned int len)
+{
+	while (len--)
+		fcs = (fcs >> 8) ^ tbl_crc24[(fcs ^ *cp++) & 0xff];
+	return fcs;
+}
diff --git a/src/gprs/gb_proxy.c b/src/gprs/gb_proxy.c
new file mode 100644
index 0000000..8df93a9
--- /dev/null
+++ b/src/gprs/gb_proxy.c
@@ -0,0 +1,684 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gb_proxy.h>
+
+struct gbprox_peer {
+	struct llist_head list;
+
+	/* NS-VC over which we send/receive data to this BVC */
+	struct gprs_nsvc *nsvc;
+
+	/* BVCI used for Point-to-Point to this peer */
+	uint16_t bvci;
+	int blocked;
+
+	/* Routeing Area that this peer is part of (raw 04.08 encoding) */
+	uint8_t ra[6];
+};
+
+/* Linked list of all Gb peers (except SGSN) */
+static LLIST_HEAD(gbprox_bts_peers);
+
+/* Find the gbprox_peer by its BVCI */
+static struct gbprox_peer *peer_by_bvci(uint16_t bvci)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (peer->bvci == bvci)
+			return peer;
+	}
+	return NULL;
+}
+
+static struct gbprox_peer *peer_by_nsvc(struct gprs_nsvc *nsvc)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (peer->nsvc == nsvc)
+			return peer;
+	}
+	return NULL;
+}
+
+/* look-up a peer by its Routeing Area Code (RAC) */
+static struct gbprox_peer *peer_by_rac(const uint8_t *ra)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (!memcmp(peer->ra, ra, 6))
+			return peer;
+	}
+	return NULL;
+}
+
+/* look-up a peer by its Location Area Code (LAC) */
+static struct gbprox_peer *peer_by_lac(const uint8_t *la)
+{
+	struct gbprox_peer *peer;
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		if (!memcmp(peer->ra, la, 5))
+			return peer;
+	}
+	return NULL;
+}
+
+static struct gbprox_peer *peer_alloc(uint16_t bvci)
+{
+	struct gbprox_peer *peer;
+
+	peer = talloc_zero(tall_bsc_ctx, struct gbprox_peer);
+	if (!peer)
+		return NULL;
+
+	peer->bvci = bvci;
+	llist_add(&peer->list, &gbprox_bts_peers);
+
+	return peer;
+}
+
+static void peer_free(struct gbprox_peer *peer)
+{
+	llist_del(&peer->list);
+	talloc_free(peer);
+}
+
+/* FIXME: this needs to go to libosmocore/msgb.c */
+static struct msgb *msgb_copy(const struct msgb *msg, const char *name)
+{
+	struct openbsc_msgb_cb *old_cb, *new_cb;
+	struct msgb *new_msg;
+
+	new_msg = msgb_alloc(msg->data_len, name);
+	if (!new_msg)
+		return NULL;
+
+	/* copy data */
+	memcpy(new_msg->_data, msg->_data, new_msg->data_len);
+
+	/* copy header */
+	new_msg->len = msg->len;
+	new_msg->data += msg->data - msg->_data;
+	new_msg->head += msg->head - msg->_data;
+	new_msg->tail += msg->tail - msg->_data;
+
+	new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data);
+	new_msg->l2h = new_msg->_data + (msg->l2h - msg->_data);
+	new_msg->l3h = new_msg->_data + (msg->l3h - msg->_data);
+	new_msg->l4h = new_msg->_data + (msg->l4h - msg->_data);
+
+	/* copy GB specific data */
+	old_cb = OBSC_MSGB_CB(msg);
+	new_cb = OBSC_MSGB_CB(new_msg);
+
+	new_cb->bssgph = new_msg->_data + (old_cb->bssgph - msg->_data);
+	new_cb->llch = new_msg->_data + (old_cb->llch - msg->_data);
+
+	/* bssgp_cell_id is a pointer into the old msgb, so we need to make
+	 * it a pointer into the new msgb */
+	new_cb->bssgp_cell_id = new_msg->_data + (old_cb->bssgp_cell_id - msg->_data);
+	new_cb->nsei = old_cb->nsei;
+	new_cb->bvci = old_cb->bvci;
+	new_cb->tlli = old_cb->tlli;
+
+	return new_msg;
+}
+
+/* strip off the NS header */
+static void strip_ns_hdr(struct msgb *msg)
+{
+	int strip_len = msgb_bssgph(msg) - msg->data;
+	msgb_pull(msg, strip_len);
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2sgsn(struct msgb *old_msg, uint16_t ns_bvci)
+{
+	/* create a copy of the message so the old one can
+	 * be free()d safely when we return from gbprox_rcvmsg() */
+	struct msgb *msg = msgb_copy(old_msg, "msgb_relay2sgsn");
+
+	DEBUGP(DGPRS, "NSEI=%u proxying BTS->SGSN (NS_BVCI=%u, NSEI=%u)\n",
+		msgb_nsei(msg), ns_bvci, gbcfg.nsip_sgsn_nsei);
+
+	msgb_bvci(msg) = ns_bvci;
+	msgb_nsei(msg) = gbcfg.nsip_sgsn_nsei;
+
+	strip_ns_hdr(msg);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* feed a message down the NS-VC associated with the specified peer */
+static int gbprox_relay2peer(struct msgb *old_msg, struct gbprox_peer *peer,
+			  uint16_t ns_bvci)
+{
+	/* create a copy of the message so the old one can
+	 * be free()d safely when we return from gbprox_rcvmsg() */
+	struct msgb *msg = msgb_copy(old_msg, "msgb_relay2peer");
+
+	DEBUGP(DGPRS, "NSEI=%u proxying SGSN->BSS (NS_BVCI=%u, NSEI=%u)\n",
+		msgb_nsei(msg), ns_bvci, peer->nsvc->nsei);
+
+	msgb_bvci(msg) = ns_bvci;
+	msgb_nsei(msg) = peer->nsvc->nsei;
+
+	/* Strip the old NS header, it will be replaced with a new one */
+	strip_ns_hdr(msg);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+static int block_unblock_peer(uint16_t ptp_bvci, uint8_t pdu_type)
+{
+	struct gbprox_peer *peer;
+
+	peer = peer_by_bvci(ptp_bvci);
+	if (!peer) {
+		LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n",
+			ptp_bvci);
+		return -ENOENT;
+	}
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_BVC_BLOCK_ACK:
+		peer->blocked = 1;
+		break;
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+		peer->blocked = 0;
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+/* Send a message to a peer identified by ptp_bvci but using ns_bvci
+ * in the NS hdr */
+static int gbprox_relay2bvci(struct msgb *msg, uint16_t ptp_bvci,
+			  uint16_t ns_bvci)
+{
+	struct gbprox_peer *peer;
+
+	peer = peer_by_bvci(ptp_bvci);
+	if (!peer) {
+		LOGP(DGPRS, LOGL_ERROR, "BVCI=%u: Cannot find BSS\n",
+			ptp_bvci);
+		return -ENOENT;
+	}
+
+	return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming signalling message from a BSS-side NS-VC */
+static int gbprox_rx_sig_from_bss(struct msgb *msg, struct gprs_nsvc *nsvc,
+				  uint16_t ns_bvci)
+{
+	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct tlv_parsed tp;
+	uint8_t pdu_type = bgph->pdu_type;
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct gbprox_peer *from_peer;
+	struct gprs_ra_id raid;
+
+	if (ns_bvci != 0 && ns_bvci != 1) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u BVCI=%u is not signalling\n",
+			nsvc->nsei, ns_bvci);
+		return -EINVAL;
+	}
+
+	/* we actually should never see those two for BVCI == 0, but double-check
+	 * just to make sure  */
+	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u UNITDATA not allowed in "
+			"signalling\n", nsvc->nsei);
+		return -EINVAL;
+	}
+
+	bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_SUSPEND:
+	case BSSGP_PDUT_RESUME:
+		/* We implement RAC snooping during SUSPEND/RESUME, since
+		 * it establishes a relationsip between BVCI/peer and the
+		 * routeing area code.  The snooped information is then
+		 * used for routing the {SUSPEND,RESUME}_[N]ACK back to
+		 * the correct BSSGP */
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+			goto err_mand_ie;
+		from_peer = peer_by_nsvc(nsvc);
+		if (!from_peer)
+			goto err_no_peer;
+		memcpy(from_peer->ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
+			sizeof(from_peer->ra));
+		gsm48_parse_ra(&raid, from_peer->ra);
+		LOGP(DGPRS, LOGL_INFO, "NSEI=%u BSSGP SUSPEND/RESUME "
+			"RAC snooping: RAC %u-%u-%u-%u behind BVCI=%u, "
+			"NSVCI=%u\n",nsvc->nsei, raid.mcc, raid.mnc, raid.lac,
+			raid.rac , from_peer->bvci, nsvc->nsvci);
+		/* FIXME: This only supports one BSS per RA */
+		break;
+	case BSSGP_PDUT_BVC_RESET:
+		/* If we receive a BVC reset on the signalling endpoint, we
+		 * don't want the SGSN to reset, as the signalling endpoint
+		 * is common for all point-to-point BVCs (and thus all BTS) */
+		if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+			uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+			LOGP(DGPRS, LOGL_INFO, "NSEI=%u Rx BVC RESET (BVCI=%u)\n",
+				nsvc->nsei, bvci);
+			if (bvci == 0) {
+				/* FIXME: only do this if SGSN is alive! */
+				LOGP(DGPRS, LOGL_INFO, "NSEI=%u Tx fake "
+					"BVC RESET ACK of BVCI=0\n", nsvc->nsei);
+				return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+							    nsvc->nsei, 0, ns_bvci);
+			}
+			from_peer = peer_by_bvci(bvci);
+			if (!from_peer) {
+				/* if a PTP-BVC is reset, and we don't know that
+				 * PTP-BVCI yet, we should allocate a new peer */
+				LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for "
+				     "BVCI=%u via NSVCI=%u/NSEI=%u\n", bvci,
+				     nsvc->nsvci, nsvc->nsei);
+				from_peer = peer_alloc(bvci);
+				from_peer->nsvc = nsvc;
+			}
+			if (TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID)) {
+				struct gprs_ra_id raid;
+				/* We have a Cell Identifier present in this
+				 * PDU, this means we can extend our local
+				 * state information about this particular cell
+				 * */
+				memcpy(from_peer->ra,
+					TLVP_VAL(&tp, BSSGP_IE_CELL_ID),
+					sizeof(from_peer->ra));
+				gsm48_parse_ra(&raid, from_peer->ra);
+				LOGP(DGPRS, LOGL_INFO, "NSEI=%u/BVCI=%u "
+				     "Cell ID %u-%u-%u-%u\n", nsvc->nsei,
+				     bvci, raid.mcc, raid.mnc, raid.lac,
+				     raid.rac);
+			}
+		}
+		break;
+	}
+
+	/* Normally, we can simply pass on all signalling messages from BSS to
+	 * SGSN */
+	return gbprox_relay2sgsn(msg, ns_bvci);
+err_no_peer:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) cannot find peer based on RAC\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+err_mand_ie:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(BSS) missing mandatory RA IE\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* Receive paging request from SGSN, we need to relay to proper BSS */
+static int gbprox_rx_paging(struct msgb *msg, struct tlv_parsed *tp,
+			    struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+	struct gbprox_peer *peer = NULL;
+
+	LOGP(DGPRS, LOGL_INFO, "NSEI=%u(SGSN) BSSGP PAGING ",
+		nsvc->nsei);
+	if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+		uint16_t bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+		LOGPC(DGPRS, LOGL_INFO, "routing by BVCI to peer BVCI=%u\n",
+			bvci);
+	} else if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
+		peer = peer_by_rac(TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
+		LOGPC(DGPRS, LOGL_INFO, "routing by RAC to peer BVCI=%u\n",
+			peer ? peer->bvci : -1);
+	} else if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) {
+		peer = peer_by_lac(TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA));
+		LOGPC(DGPRS, LOGL_INFO, "routing by LAC to peer BVCI=%u\n",
+			peer ? peer->bvci : -1);
+	} else
+		LOGPC(DGPRS, LOGL_INFO, "\n");
+
+	if (!peer) {
+		LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) BSSGP PAGING: "
+			"unable to route, missing IE\n", nsvc->nsei);
+		return -EINVAL;
+	}
+	return gbprox_relay2peer(msg, peer, ns_bvci);
+}
+
+/* Receive an incoming BVC-RESET message from the SGSN */
+static int rx_reset_from_sgsn(struct msgb *msg, struct tlv_parsed *tp,
+			      struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+	struct gbprox_peer *peer;
+	uint16_t ptp_bvci;
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE,
+				       NULL, msg);
+	}
+	ptp_bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+
+	if (ptp_bvci >= 2) {
+		/* A reset for a PTP BVC was received, forward it to its
+		 * respective peer */
+		peer = peer_by_bvci(ptp_bvci);
+		if (!peer) {
+			LOGP(DGPRS, LOGL_ERROR, "NSEI=%u BVCI=%u: Cannot find BSS\n",
+				nsvc->nsei, ptp_bvci);
+			return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI,
+					       NULL, msg);
+		}
+		return gbprox_relay2peer(msg, peer, ns_bvci);
+	}
+
+	/* A reset for the Signalling entity has been received
+	 * from the SGSN.  As the signalling BVCI is shared
+	 * among all the BSS's that we multiplex, it needs to
+	 * be relayed  */
+	llist_for_each_entry(peer, &gbprox_bts_peers, list)
+		gbprox_relay2peer(msg, peer, ns_bvci);
+
+	return 0;
+}
+
+/* Receive an incoming signalling message from the SGSN-side NS-VC */
+static int gbprox_rx_sig_from_sgsn(struct msgb *msg, struct gprs_nsvc *nsvc,
+				   uint16_t ns_bvci)
+{
+	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct tlv_parsed tp;
+	uint8_t pdu_type = bgph->pdu_type;
+	int data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+	struct gbprox_peer *peer;
+	uint16_t bvci;
+	int rc = 0;
+
+	if (ns_bvci != 0 && ns_bvci != 1) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BVCI=%u is not "
+			"signalling\n", nsvc->nsei, ns_bvci);
+		/* FIXME: Send proper error message */
+		return -EINVAL;
+	}
+
+	/* we actually should never see those two for BVCI == 0, but double-check
+	 * just to make sure  */
+	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) UNITDATA not allowed in "
+			"signalling\n", nsvc->nsei);
+		return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+	}
+
+	rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_BVC_RESET:
+		rc = rx_reset_from_sgsn(msg, &tp, nsvc, ns_bvci);
+		break;
+	case BSSGP_PDUT_FLUSH_LL:
+	case BSSGP_PDUT_BVC_RESET_ACK:
+		/* simple case: BVCI IE is mandatory */
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		rc = gbprox_relay2bvci(msg, bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_PAGING_PS:
+	case BSSGP_PDUT_PAGING_CS:
+		/* process the paging request (LAC/RAC lookup) */
+		rc = gbprox_rx_paging(msg, &tp, nsvc, ns_bvci);
+		break;
+	case BSSGP_PDUT_STATUS:
+		/* Some exception has occurred */
+		LOGP(DGPRS, LOGL_NOTICE,
+			"NSEI=%u(SGSN) BSSGP STATUS ", nsvc->nsei);
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) {
+			LOGPC(DGPRS, LOGL_NOTICE, "\n");
+			goto err_mand_ie;
+		}
+		LOGPC(DGPRS, LOGL_NOTICE,
+			"cause=0x%02x(%s) ", *TLVP_VAL(&tp, BSSGP_IE_CAUSE),
+			bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE)));
+		if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+			uint16_t *bvci = (uint16_t *)
+						TLVP_VAL(&tp, BSSGP_IE_BVCI);
+			LOGPC(DGPRS, LOGL_NOTICE,
+				"BVCI=%u\n", ntohs(*bvci));
+		} else
+			LOGPC(DGPRS, LOGL_NOTICE, "\n");
+		break;
+	/* those only exist in the SGSN -> BSS direction */
+	case BSSGP_PDUT_SUSPEND_ACK:
+	case BSSGP_PDUT_SUSPEND_NACK:
+	case BSSGP_PDUT_RESUME_ACK:
+	case BSSGP_PDUT_RESUME_NACK:
+		/* RAC IE is mandatory */
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA))
+			goto err_mand_ie;
+		peer = peer_by_rac(TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA));
+		if (!peer)
+			goto err_no_peer;
+		rc = gbprox_relay2peer(msg, peer, ns_bvci);
+		break;
+	case BSSGP_PDUT_BVC_BLOCK_ACK:
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+			goto err_mand_ie;
+		bvci = ntohs(*(uint16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI));
+		if (bvci == 0) {
+			LOGP(DGPRS, LOGL_NOTICE, "NSEI=%u(SGSN) BSSGP "
+			     "%sBLOCK_ACK for signalling BVCI ?!?\n", nsvc->nsei,
+			     pdu_type == BSSGP_PDUT_BVC_UNBLOCK_ACK ? "UN":"");
+			/* should we send STATUS ? */
+		} else {
+			/* Mark BVC as (un)blocked */
+			block_unblock_peer(bvci, pdu_type);
+		}
+		rc = gbprox_relay2bvci(msg, bvci, ns_bvci);
+		break;
+	case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+		LOGP(DGPRS, LOGL_ERROR,
+		     "NSEI=%u(SGSN) BSSGP INVOKE TRACE not supported\n",nsvc->nsei);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
+		break;
+	default:
+		LOGP(DGPRS, LOGL_NOTICE, "BSSGP PDU type 0x%02x unknown\n",
+			pdu_type);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+		break;
+	}
+
+	return rc;
+err_mand_ie:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) missing mandatory IE\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+err_no_peer:
+	LOGP(DGPRS, LOGL_ERROR, "NSEI=%u(SGSN) cannot find peer based on RAC\n",
+		nsvc->nsei);
+	return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+}
+
+/* Main input function for Gb proxy */
+int gbprox_rcvmsg(struct msgb *msg, struct gprs_nsvc *nsvc, uint16_t ns_bvci)
+{
+	int rc;
+	struct gbprox_peer *peer;
+
+	/* Only BVCI=0 messages need special treatment */
+	if (ns_bvci == 0 || ns_bvci == 1) {
+		if (nsvc->remote_end_is_sgsn)
+			rc = gbprox_rx_sig_from_sgsn(msg, nsvc, ns_bvci);
+		else
+			rc = gbprox_rx_sig_from_bss(msg, nsvc, ns_bvci);
+	} else {
+		/* All other BVCI are PTP and thus can be simply forwarded */
+		if (!nsvc->remote_end_is_sgsn) {
+			return gbprox_relay2sgsn(msg, ns_bvci);
+		}
+		/* else: SGSN -> BSS direction */
+		peer = peer_by_bvci(ns_bvci);
+		if (!peer) {
+			LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for "
+			     "BVCI=%u via NSVC=%u/NSEI=%u\n", ns_bvci,
+			     nsvc->nsvci, nsvc->nsei);
+			peer = peer_alloc(ns_bvci);
+			peer->nsvc = nsvc;
+		}
+		if (peer->blocked) {
+			LOGP(DGPRS, LOGL_NOTICE, "Dropping PDU for "
+			     "blocked BVCI=%u via NSVC=%u/NSEI=%u\n",
+			     ns_bvci, nsvc->nsvci, nsvc->nsei);
+			return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, NULL, msg);
+		}
+		rc = gbprox_relay2peer(msg, peer, ns_bvci);
+	}
+
+	return rc;
+}
+
+int gbprox_reset_persistent_nsvcs(struct gprs_ns_inst *nsi)
+{
+	struct gprs_nsvc *nsvc;
+
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (!nsvc->persistent)
+			continue;
+		gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+	}
+	return 0;
+}
+
+/* Signal handler for signals from NS layer */
+int gbprox_signal(unsigned int subsys, unsigned int signal,
+		  void *handler_data, void *signal_data)
+{
+	struct ns_signal_data *nssd = signal_data;
+	struct gprs_nsvc *nsvc = nssd->nsvc;
+	struct gbprox_peer *peer;
+
+	if (subsys != SS_NS)
+		return 0;
+
+	if (signal == S_NS_RESET && nsvc->nsei == gbcfg.nsip_sgsn_nsei) {
+		/* We have received a NS-RESET from the NSEI and NSVC
+		 * of the SGSN.  This might happen with SGSN that start
+		 * their own NS-RESET procedure without waiting for our
+		 * NS-RESET */
+		nsvc->remote_end_is_sgsn = 1;
+	}
+
+	if (signal == S_NS_ALIVE_EXP && nsvc->remote_end_is_sgsn) {
+		LOGP(DGPRS, LOGL_NOTICE, "Tns alive expired too often, "
+			"re-starting RESET procedure\n");
+		nsip_connect(nsvc->nsi, &nsvc->ip.bts_addr, nsvc->nsei,
+			     nsvc->nsvci);
+	}
+
+	if (!nsvc->remote_end_is_sgsn) {
+		/* from BSS to SGSN */
+		peer = peer_by_nsvc(nsvc);
+		if (!peer) {
+			LOGP(DGPRS, LOGL_NOTICE, "signal %u for unknown peer "
+			     "NSEI=%u/NSVCI=%u\n", signal, nsvc->nsei,
+			     nsvc->nsvci);
+			return 0;
+		}
+		switch (signal) {
+		case S_NS_RESET:
+		case S_NS_BLOCK:
+			if (!peer->blocked)
+				break;
+			LOGP(DGPRS, LOGL_NOTICE, "Converting NS_RESET from "
+			     "NSEI=%u/NSVCI=%u into BSSGP_BVC_BLOCK to SGSN\n",
+			     nsvc->nsei, nsvc->nsvci);
+			bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, nsvc->nsei,
+					     peer->bvci, 0);
+			break;
+		}
+	} else {
+		/* iterate over all BTS peers and send the respective PDU */
+		llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+			switch (signal) {
+			case S_NS_RESET:
+				gprs_ns_tx_reset(peer->nsvc, nssd->cause);
+				break;
+			case S_NS_BLOCK:
+				gprs_ns_tx_block(peer->nsvc, nssd->cause);
+				break;
+			case S_NS_UNBLOCK:
+				gprs_ns_tx_unblock(peer->nsvc);
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+
+#include <osmocom/vty/command.h>
+
+gDEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy",
+       SHOW_STR "Display information about the Gb proxy")
+{
+	struct gbprox_peer *peer;
+
+	llist_for_each_entry(peer, &gbprox_bts_peers, list) {
+		struct gprs_nsvc *nsvc = peer->nsvc;
+		struct gprs_ra_id raid;
+		gsm48_parse_ra(&raid, peer->ra);
+
+		vty_out(vty, "NSEI %5u, NS-VC %5u, PTP-BVCI %5u, "
+			"RAC %u-%u-%u-%u",
+			nsvc->nsei, nsvc->nsvci, peer->bvci,
+			raid.mcc, raid.mnc, raid.lac, raid.rac);
+		if (nsvc->ll == GPRS_NS_LL_UDP || nsvc->ll == GPRS_NS_LL_FR_GRE)
+			vty_out(vty, " %s:%u",
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				ntohs(nsvc->ip.bts_addr.sin_port));
+		if (peer->blocked)
+			vty_out(vty, " [BVC-BLOCKED]");
+
+		vty_out(vty, "%s", VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
diff --git a/src/gprs/gb_proxy_main.c b/src/gprs/gb_proxy_main.c
new file mode 100644
index 0000000..b53e985
--- /dev/null
+++ b/src/gprs/gb_proxy_main.c
@@ -0,0 +1,288 @@
+/* NS-over-IP proxy */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <osmocore/process.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/vty.h>
+#include <openbsc/gb_proxy.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/telnet_interface.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Harald Welte and On-Waves\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static struct log_target *stderr_target;
+static char *config_file = "osmo_gbproxy.cfg";
+struct gbproxy_config gbcfg;
+static int daemonize = 0;
+
+/* Pointer to the SGSN peer */
+extern struct gbprox_peer *gbprox_peer_sgsn;
+
+/* call-back function for the NS protocol */
+static int proxy_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+		      struct msgb *msg, u_int16_t bvci)
+{
+	int rc = 0;
+
+	switch (event) {
+	case GPRS_NS_EVT_UNIT_DATA:
+		rc = gbprox_rcvmsg(msg, nsvc, bvci);
+		break;
+	default:
+		LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+		if (msg)
+			talloc_free(msg);
+		rc = -EIO;
+		break;
+	}
+	return rc;
+}
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+		sleep(1);
+		exit(0);
+		break;
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		talloc_report_full(tall_vty_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+static void print_usage()
+{
+	printf("Usage: bsc_hack\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -d option --debug=DNS:DGPRS,0:0 enable debugging\n");
+	printf("  -D --daemonize Fork the process into a background daemon\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -s --disable-color\n");
+	printf("  -T --timestamp Prefix every log line with a timestamp\n");
+	printf("  -V --version. Print the version of OpenBSC.\n");
+	printf("  -e --log-level number. Set a global loglevel.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{ "help", 0, 0, 'h' },
+			{ "debug", 1, 0, 'd' },
+			{ "daemonize", 0, 0, 'D' },
+			{ "config-file", 1, 0, 'c' },
+			{ "disable-color", 0, 0, 's' },
+			{ "timestamp", 0, 0, 'T' },
+			{ "version", 0, 0, 'V' },
+			{ "log-level", 1, 0, 'e' },
+			{ 0, 0, 0, 0 }
+		};
+
+		c = getopt_long(argc, argv, "hd:Dc:sTVe:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'c':
+			config_file = strdup(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'e':
+			log_set_log_level(stderr_target, atoi(optarg));
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+extern void *tall_msgb_ctx;
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OsmoGbProxy",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	int rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 0, "nsip_proxy");
+	tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	signal(SIGPIPE, SIG_IGN);
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	logging_vty_add_cmds();
+	gbproxy_vty_init();
+
+	handle_options(argc, argv);
+
+	rate_ctr_init(tall_bsc_ctx);
+
+	rc = telnet_init(tall_bsc_ctx, &dummy_network, 4246);
+	if (rc < 0)
+		exit(1);
+
+	bssgp_nsi = gprs_ns_instantiate(&proxy_ns_cb);
+	if (!bssgp_nsi) {
+		LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+		exit(1);
+	}
+	gbcfg.nsi = bssgp_nsi;
+	gprs_ns_vty_init(bssgp_nsi);
+	register_signal_handler(SS_NS, &gbprox_signal, NULL);
+
+	rc = gbproxy_parse_config(config_file, &gbcfg);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+		exit(2);
+	}
+
+	if (!nsvc_by_nsei(gbcfg.nsi, gbcfg.nsip_sgsn_nsei)) {
+		LOGP(DGPRS, LOGL_FATAL, "You cannot proxy to NSEI %u "
+			"without creating that NSEI before\n",
+			gbcfg.nsip_sgsn_nsei);
+		exit(2);
+	}
+
+	rc = gprs_ns_nsip_listen(bssgp_nsi);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n");
+		exit(2);
+	}
+
+	rc = gprs_ns_frgre_listen(bssgp_nsi);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE "
+			"socket. Do you have CAP_NET_RAW?\n");
+		exit(2);
+	}
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	/* Reset all the persistent NS-VCs that we've read from the config */
+	gbprox_reset_persistent_nsvcs(bssgp_nsi);
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
diff --git a/src/gprs/gb_proxy_vty.c b/src/gprs/gb_proxy_vty.c
new file mode 100644
index 0000000..05f5b1e
--- /dev/null
+++ b/src/gprs/gb_proxy_vty.c
@@ -0,0 +1,104 @@
+/*
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gb_proxy.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+static struct gbproxy_config *g_cfg = NULL;
+
+/*
+ * vty code for mgcp below
+ */
+static struct cmd_node gbproxy_node = {
+	GBPROXY_NODE,
+	"%s(gbproxy)#",
+	1,
+};
+
+static int config_write_gbproxy(struct vty *vty)
+{
+	vty_out(vty, "gbproxy%s", VTY_NEWLINE);
+
+	vty_out(vty, " sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei,
+		VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy,
+      cfg_gbproxy_cmd,
+      "gbproxy",
+      "Configure the Gb proxy")
+{
+	vty->node = GBPROXY_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_sgsn_nsei,
+      cfg_nsip_sgsn_nsei_cmd,
+      "sgsn nsei <0-65534>",
+      "Set the NSEI to be used in the connection with the SGSN")
+{
+	unsigned int port = atoi(argv[0]);
+
+	g_cfg->nsip_sgsn_nsei = port;
+	return CMD_SUCCESS;
+}
+
+int gbproxy_vty_init(void)
+{
+	install_element_ve(&show_gbproxy_cmd);
+
+	install_element(CONFIG_NODE, &cfg_gbproxy_cmd);
+	install_node(&gbproxy_node, config_write_gbproxy);
+	install_default(GBPROXY_NODE);
+	install_element(GBPROXY_NODE, &ournode_exit_cmd);
+	install_element(GBPROXY_NODE, &ournode_end_cmd);
+	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd);
+
+	return 0;
+}
+
+int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg)
+{
+	int rc;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file, NULL);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	return 0;
+}
+
diff --git a/src/gprs/gprs_gmm.c b/src/gprs/gprs_gmm.c
new file mode 100644
index 0000000..949cd96
--- /dev/null
+++ b/src/gprs/gprs_gmm.c
@@ -0,0 +1,1597 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/db.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/signal.h>
+#include <osmocore/talloc.h>
+#include <osmocore/rate_ctr.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/paging.h>
+#include <openbsc/transaction.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/sgsn.h>
+
+#include <pdp.h>
+
+#define PTMSI_ALLOC
+
+/* Section 11.2.2 / Table 11.4 MM timers netowkr side */
+#define GSM0408_T3322_SECS	6	/* DETACH_REQ -> DETACH_ACC */
+#define GSM0408_T3350_SECS	6	/* waiting for ATT/RAU/TMSI COMPL */
+#define GSM0408_T3360_SECS	6	/* waiting for AUTH/CIPH RESP */
+#define GSM0408_T3370_SECS	6	/* waiting for ID RESP */
+
+/* Section 11.2.2 / Table 11.4a MM timers netowkr side */
+#define GSM0408_T3313_SECS	30	/* waiting for paging response */
+#define GSM0408_T3314_SECS	44	/* force to STBY on expiry */
+#define GSM0408_T3316_SECS	44
+
+/* Section 11.3 / Table 11.2d Timers of Session Management - network side */
+#define GSM0408_T3385_SECS	8	/* wait for ACT PDP CTX REQ */
+#define GSM0408_T3386_SECS	8	/* wait for MODIFY PDP CTX ACK */
+#define GSM0408_T3395_SECS	8	/* wait for DEACT PDP CTX ACK */
+#define GSM0408_T3397_SECS	8	/* wait for DEACT AA PDP CTX ACK */
+
+extern struct sgsn_instance *sgsn;
+
+/* Protocol related stuff, should go into libosmocore */
+
+/* 10.5.5.14 GPRS MM Cause / Table 10.5.147 */
+const struct value_string gmm_cause_names[] = {
+	{ GMM_CAUSE_IMSI_UNKNOWN, 	"IMSI unknown in HLR" },
+	{ GMM_CAUSE_ILLEGAL_MS, 	"Illegal MS" },
+	{ GMM_CAUSE_ILLEGAL_ME,		"Illegal ME" },
+	{ GMM_CAUSE_GPRS_NOTALLOWED,	"GPRS services not allowed" },
+	{ GMM_CAUSE_GPRS_OTHER_NOTALLOWED,
+			"GPRS services and non-GPRS services not allowed" },
+	{ GMM_CAUSE_MS_ID_NOT_DERIVED,
+			"MS identity cannot be derived by the network" },
+	{ GMM_CAUSE_IMPL_DETACHED,	"Implicitly detached" },
+	{ GMM_CAUSE_PLMN_NOTALLOWED,	"PLMN not allowed" },
+	{ GMM_CAUSE_LA_NOTALLOWED,	"Location Area not allowed" },
+	{ GMM_CAUSE_ROAMING_NOTALLOWED,
+			"Roaming not allowed in this location area" },
+	{ GMM_CAUSE_NO_GPRS_PLMN,
+				"GPRS services not allowed in this PLMN" },
+	{ GMM_CAUSE_MSC_TEMP_NOTREACH,	"MSC temporarily not reachable" },
+	{ GMM_CAUSE_NET_FAIL,		"Network failure" },
+	{ GMM_CAUSE_CONGESTION,		"Congestion" },
+	{ GMM_CAUSE_SEM_INCORR_MSG,	"Semantically incorrect message" },
+	{ GMM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
+	{ GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
+			"Message type non-existant or not implemented" },
+	{ GMM_CAUSE_MSGT_INCOMP_P_STATE,
+			"Message type not compatible with protocol state" },
+	{ GMM_CAUSE_IE_NOTEXIST_NOTIMPL,
+			"Information element non-existent or not implemented" },
+	{ GMM_CAUSE_COND_IE_ERR,	"Conditional IE error" },
+	{ GMM_CAUSE_MSG_INCOMP_P_STATE,
+				"Message not compatible with protocol state " },
+	{ GMM_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error, unspecified" },
+	{ 0, NULL }
+};
+
+/* 10.5.6.6 SM Cause / Table 10.5.157 */
+const struct value_string gsm_cause_names[] = {
+	{ GSM_CAUSE_INSUFF_RSRC, "Insufficient resources" },
+	{ GSM_CAUSE_MISSING_APN, "Missing or unknown APN" },
+	{ GSM_CAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" },
+	{ GSM_CAUSE_AUTH_FAILED, "User Authentication failed" },
+	{ GSM_CAUSE_ACT_REJ_GGSN, "Activation rejected by GGSN" },
+	{ GSM_CAUSE_ACT_REJ_UNSPEC, "Activation rejected, unspecified" },
+	{ GSM_CAUSE_SERV_OPT_NOTSUPP, "Service option not supported" },
+	{ GSM_CAUSE_REQ_SERV_OPT_NOTSUB,
+				"Requested service option not subscribed" },
+	{ GSM_CAUSE_SERV_OPT_TEMP_OOO,
+				"Service option temporarily out of order" },
+	{ GSM_CAUSE_NSAPI_IN_USE, "NSAPI already used" },
+	{ GSM_CAUSE_DEACT_REGULAR, "Regular deactivation" },
+	{ GSM_CAUSE_QOS_NOT_ACCEPTED, "QoS not accepted" },
+	{ GSM_CAUSE_NET_FAIL, "Network Failure" },
+	{ GSM_CAUSE_REACT_RQD, "Reactivation required" },
+	{ GSM_CAUSE_FEATURE_NOTSUPP, "Feature not supported " },
+	{ GSM_CAUSE_INVALID_TRANS_ID, "Invalid transaction identifier" },
+	{ GSM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
+	{ GSM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
+	{ GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
+			"Message type non-existant or not implemented" },
+	{ GSM_CAUSE_MSGT_INCOMP_P_STATE,
+			"Message type not compatible with protocol state" },
+	{ GSM_CAUSE_IE_NOTEXIST_NOTIMPL,
+			"Information element non-existent or not implemented" },
+	{ GSM_CAUSE_COND_IE_ERR, "Conditional IE error" },
+	{ GSM_CAUSE_MSG_INCOMP_P_STATE,
+				"Message not compatible with protocol state " },
+	{ GSM_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+	{ 0, NULL }
+};
+
+/* 10.5.5.2 */
+const struct value_string gprs_att_t_strs[] = {
+	{ GPRS_ATT_T_ATTACH, 		"GPRS attach" },
+	{ GPRS_ATT_T_ATT_WHILE_IMSI, 	"GPRS attach while IMSI attached" },
+	{ GPRS_ATT_T_COMBINED, 		"Combined GPRS/IMSI attach" },
+	{ 0, NULL }
+};
+
+const struct value_string gprs_upd_t_strs[] = {
+	{ GPRS_UPD_T_RA,		"RA updating" },
+	{ GPRS_UPD_T_RA_LA,		"combined RA/LA updating" },
+	{ GPRS_UPD_T_RA_LA_IMSI_ATT,	"combined RA/LA updating + IMSI attach" },
+	{ GPRS_UPD_T_PERIODIC,		"periodic updating" },
+	{ 0, NULL }
+};
+
+/* 10.5.5.5 */
+const struct value_string gprs_det_t_mo_strs[] = {
+	{ GPRS_DET_T_MO_GPRS,		"GPRS detach" },
+	{ GPRS_DET_T_MO_IMSI,		"IMSI detach" },
+	{ GPRS_DET_T_MO_COMBINED,	"Combined GPRS/IMSI detach" },
+	{ 0, NULL }
+};
+
+static const struct tlv_definition gsm48_gmm_att_tlvdef = {
+	.def = {
+		[GSM48_IE_GMM_CIPH_CKSN]	= { TLV_TYPE_FIXED, 1 },
+		[GSM48_IE_GMM_TIMER_READY]	= { TLV_TYPE_TV, 1 },
+		[GSM48_IE_GMM_ALLOC_PTMSI]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GMM_PTMSI_SIG]	= { TLV_TYPE_FIXED, 3 },
+		[GSM48_IE_GMM_AUTH_RAND]	= { TLV_TYPE_FIXED, 16 },
+		[GSM48_IE_GMM_AUTH_SRES]	= { TLV_TYPE_FIXED, 4 },
+		[GSM48_IE_GMM_IMEISV]		= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GMM_DRX_PARAM]	= { TLV_TYPE_FIXED, 2 },
+		[GSM48_IE_GMM_MS_NET_CAPA]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GMM_PDP_CTX_STATUS]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GMM_PS_LCS_CAPA]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GMM_GMM_MBMS_CTX_ST]	= { TLV_TYPE_TLV, 0 },
+	},
+};
+
+static const struct tlv_definition gsm48_sm_att_tlvdef = {
+	.def = {
+		[GSM48_IE_GSM_APN]		= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GSM_PROTO_CONF_OPT]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GSM_PDP_ADDR]		= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GSM_AA_TMR]		= { TLV_TYPE_TV, 1 },
+		[GSM48_IE_GSM_NAME_FULL]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GSM_NAME_SHORT]	= { TLV_TYPE_TLV, 0 },
+		[GSM48_IE_GSM_TIMEZONE]		= { TLV_TYPE_FIXED, 1 },
+		[GSM48_IE_GSM_UTC_AND_TZ]	= { TLV_TYPE_FIXED, 7 },
+		[GSM48_IE_GSM_LSA_ID]		= { TLV_TYPE_TLV, 0 },
+	},
+};
+
+/* Our implementation, should be kept in SGSN */
+
+static void mmctx_timer_cb(void *_mm);
+
+static void mmctx_timer_start(struct sgsn_mm_ctx *mm, unsigned int T,
+				unsigned int seconds)
+{
+	if (bsc_timer_pending(&mm->timer))
+		LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old "
+			"timer %u pending\n", T, mm->T);
+	mm->T = T;
+	mm->num_T_exp = 0;
+
+	/* FIXME: we should do this only once ? */
+	mm->timer.data = mm;
+	mm->timer.cb = &mmctx_timer_cb;
+
+	bsc_schedule_timer(&mm->timer, seconds, 0);
+}
+
+static void mmctx_timer_stop(struct sgsn_mm_ctx *mm, unsigned int T)
+{
+	if (mm->T != T)
+		LOGP(DMM, LOGL_ERROR, "Stopping MM timer %u but "
+			"%u is running\n", T, mm->T);
+	bsc_del_timer(&mm->timer);
+}
+
+/* Send a message through the underlying layer */
+static int gsm48_gmm_sendmsg(struct msgb *msg, int command,
+			     const struct sgsn_mm_ctx *mm)
+{
+	if (mm)
+		rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_SIG_OUT]);
+
+	/* caller needs to provide TLLI, BVCI and NSEI */
+	return gprs_llc_tx_ui(msg, GPRS_SAPI_GMM, command, mm);
+}
+
+/* copy identifiers from old message to new message, this
+ * is required so lower layers can route it correctly */
+static void gmm_copy_id(struct msgb *msg, const struct msgb *old)
+{
+	msgb_tlli(msg) = msgb_tlli(old);
+	msgb_bvci(msg) = msgb_bvci(old);
+	msgb_nsei(msg) = msgb_nsei(old);
+}
+
+/* Store BVCI/NSEI in MM context */
+static void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg)
+{
+	mm->bvci = msgb_bvci(msg);
+	mm->nsei = msgb_nsei(msg);
+}
+
+/* Store BVCI/NSEI in MM context */
+static void mmctx2msgid(struct msgb *msg, const struct sgsn_mm_ctx *mm)
+{
+	msgb_tlli(msg) = mm->tlli;
+	msgb_bvci(msg) = mm->bvci;
+	msgb_nsei(msg) = mm->nsei;
+}
+
+/* Chapter 9.4.18 */
+static int _tx_status(struct msgb *msg, uint8_t cause,
+		      struct sgsn_mm_ctx *mmctx, int sm)
+{
+	struct gsm48_hdr *gh;
+
+	/* MMCTX might be NULL! */
+
+	DEBUGP(DMM, "<- GPRS MM STATUS (cause: %s)\n",
+		get_value_string(gmm_cause_names, cause));
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	if (sm) {
+		gh->proto_discr = GSM48_PDISC_SM_GPRS;
+		gh->msg_type = GSM48_MT_GSM_STATUS;
+	} else {
+		gh->proto_discr = GSM48_PDISC_MM_GPRS;
+		gh->msg_type = GSM48_MT_GMM_STATUS;
+	}
+	gh->data[0] = cause;
+
+	return gsm48_gmm_sendmsg(msg, 0, mmctx);
+}
+static int gsm48_tx_gmm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+
+	mmctx2msgid(msg, mmctx);
+	return _tx_status(msg, cause, mmctx, 0);
+};
+static int gsm48_tx_gmm_status_oldmsg(struct msgb *oldmsg, uint8_t cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+
+	gmm_copy_id(msg, oldmsg);
+	return _tx_status(msg, cause, NULL, 0);
+}
+static int gsm48_tx_sm_status(struct sgsn_mm_ctx *mmctx, uint8_t cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+
+	mmctx2msgid(msg, mmctx);
+	return _tx_status(msg, cause, mmctx, 1);
+};
+static int gsm48_tx_sm_status_oldmsg(struct msgb *oldmsg, uint8_t cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+
+	gmm_copy_id(msg, oldmsg);
+	return _tx_status(msg, cause, NULL, 1);
+}
+
+
+static struct gsm48_qos default_qos = {
+	.delay_class = 4,	/* best effort */
+	.reliab_class = GSM48_QOS_RC_LLC_UN_RLC_ACK_DATA_PROT,
+	.peak_tput = GSM48_QOS_PEAK_TPUT_32000bps,
+	.preced_class = GSM48_QOS_PC_NORMAL,
+	.mean_tput = GSM48_QOS_MEAN_TPUT_BEST_EFFORT,
+	.traf_class = GSM48_QOS_TC_INTERACTIVE,
+	.deliv_order = GSM48_QOS_DO_UNORDERED,
+	.deliv_err_sdu = GSM48_QOS_ERRSDU_YES,
+	.max_sdu_size = GSM48_QOS_MAXSDU_1520,
+	.max_bitrate_up = GSM48_QOS_MBRATE_63k,
+	.max_bitrate_down = GSM48_QOS_MBRATE_63k,
+	.resid_ber = GSM48_QOS_RBER_5e_2,
+	.sdu_err_ratio = GSM48_QOS_SERR_1e_2,
+	.handling_prio = 3,
+	.xfer_delay = 0x10,	/* 200ms */
+	.guar_bitrate_up = GSM48_QOS_MBRATE_0k,
+	.guar_bitrate_down = GSM48_QOS_MBRATE_0k,
+	.sig_ind = 0,	/* not optimised for signalling */
+	.max_bitrate_down_ext = 0,	/* use octet 9 */
+	.guar_bitrate_down_ext = 0,	/* use octet 13 */
+};
+
+/* Chapter 9.4.2: Attach accept */
+static int gsm48_tx_gmm_att_ack(struct sgsn_mm_ctx *mm)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_attach_ack *aa;
+	uint8_t *ptsig, *mid;
+
+	DEBUGP(DMM, "<- GPRS ATTACH ACCEPT (new P-TMSI=0x%08x)\n", mm->p_tmsi);
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ATTACH_ACK;
+
+	aa = (struct gsm48_attach_ack *) msgb_put(msg, sizeof(*aa));
+	aa->force_stby = 0;	/* not indicated */
+	aa->att_result = 1;	/* GPRS only */
+	aa->ra_upd_timer = GPRS_TMR_MINUTE | 10;
+	aa->radio_prio = 4;	/* lowest */
+	gsm48_construct_ra(aa->ra_id.digits, &mm->ra);
+
+#if 0
+	/* Optional: P-TMSI signature */
+	msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG);
+	ptsig = msgb_put(msg, 3);
+	ptsig[0] = mm->p_tmsi_sig >> 16;
+	ptsig[1] = mm->p_tmsi_sig >> 8;
+	ptsig[2] = mm->p_tmsi_sig & 0xff;
+
+	/* Optional: Negotiated Ready timer value */
+#endif
+
+#ifdef PTMSI_ALLOC
+	/* Optional: Allocated P-TMSI */
+	mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+	gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi);
+	mid[0] = GSM48_IE_GMM_ALLOC_PTMSI;
+#endif
+
+	/* Optional: MS-identity (combined attach) */
+	/* Optional: GMM cause (partial attach result for combined attach) */
+
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Chapter 9.4.5: Attach reject */
+static int _tx_gmm_att_rej(struct msgb *msg, uint8_t gmm_cause)
+{
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- GPRS ATTACH REJECT\n");
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ATTACH_REJ;
+	gh->data[0] = gmm_cause;
+
+	return gsm48_gmm_sendmsg(msg, 0, NULL);
+}
+static int gsm48_tx_gmm_att_rej_oldmsg(const struct msgb *old_msg,
+					uint8_t gmm_cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	gmm_copy_id(msg, old_msg);
+	return _tx_gmm_att_rej(msg, gmm_cause);
+}
+static int gsm48_tx_gmm_att_rej(struct sgsn_mm_ctx *mm,
+				uint8_t gmm_cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	mmctx2msgid(msg, mm);
+	return _tx_gmm_att_rej(msg, gmm_cause);
+}
+
+/* Chapter 9.4.6.2 Detach accept */
+static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- GPRS DETACH ACCEPT\n");
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_DETACH_ACK;
+	gh->data[0] = force_stby;
+
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Transmit Chapter 9.4.12 Identity Request */
+static int gsm48_tx_gmm_id_req(struct sgsn_mm_ctx *mm, uint8_t id_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- GPRS IDENTITY REQUEST: mi_type=%02x\n", id_type);
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ID_REQ;
+	/* 10.5.5.9 ID type 2 + identity type and 10.5.5.7 'force to standby' IE */
+	gh->data[0] = id_type & 0xf;
+
+	return gsm48_gmm_sendmsg(msg, 1, mm);
+}
+
+/* Section 9.4.9: Authentication and Ciphering Request */
+static int gsm48_tx_gmm_auth_ciph_req(struct sgsn_mm_ctx *mm, uint8_t *rand,
+				      uint8_t key_seq, uint8_t algo)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_auth_ciph_req *acreq;
+	uint8_t *m_rand, *m_cksn;
+
+	DEBUGP(DMM, "<- GPRS AUTH AND CIPHERING REQ (rand = %s)\n",
+		hexdump(rand, 16));
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REQ;
+
+	acreq = (struct gsm48_auth_ciph_req *) msgb_put(msg, sizeof(*acreq));
+	acreq->ciph_alg = algo & 0xf;
+	acreq->imeisv_req = 0x1;
+	acreq->force_stby = 0x0;
+	acreq->ac_ref_nr = 0x0;	/* FIXME: increment this? */
+
+	/* Only if authentication is requested we need to set RAND + CKSN */
+	if (rand) {
+		m_rand = msgb_put(msg, 16+1);
+		m_rand[0] = GSM48_IE_GMM_AUTH_RAND;
+		memcpy(m_rand+1, rand, 16);
+
+		m_cksn = msgb_put(msg, 1+1);
+		m_cksn[0] = GSM48_IE_GMM_CIPH_CKSN;
+		m_cksn[1] = key_seq;
+	}
+
+	/* Start T3360 */
+	mmctx_timer_start(mm, 3360, GSM0408_T3360_SECS);
+
+	/* FIXME: make sure we don't send any other messages to the MS */
+
+	return gsm48_gmm_sendmsg(msg, 1, mm);
+}
+
+/* Section 9.4.11: Authentication and Ciphering Reject */
+static int gsm48_tx_gmm_auth_ciph_rej(struct sgsn_mm_ctx *mm)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- GPRS AUTH AND CIPH REJECT\n");
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_AUTH_CIPH_REJ;
+
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Section 9.4.10: Authentication and Ciphering Response */
+static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx,
+					struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct gsm48_auth_ciph_resp *acr = (struct gsm48_auth_ciph_resp *)gh->data;
+	struct tlv_parsed tp;
+	int rc;
+
+	/* FIXME: Stop T3360 */
+
+	rc = tlv_parse(&tp, &gsm48_gmm_att_tlvdef, acr->data,
+			(msg->data + msg->len) - acr->data, 0, 0);
+
+	/* FIXME: compare ac_ref? */
+
+	if (!TLVP_PRESENT(&tp, GSM48_IE_GMM_AUTH_SRES) ||
+	    !TLVP_PRESENT(&tp, GSM48_IE_GMM_IMEISV)) {
+		/* FIXME: missing mandatory IE */
+	}
+
+	/* FIXME: compare SRES with what we expected */
+	/* FIXME: enable LLC cipheirng */
+	return 0;
+}
+
+/* Check if we can already authorize a subscriber */
+static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx,
+				enum gprs_t3350_mode t3350_mode)
+{
+	if (strlen(ctx->imei) && strlen(ctx->imsi)) {
+#ifdef PTMSI_ALLOC
+		/* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */
+		ctx->t3350_mode = t3350_mode;
+		mmctx_timer_start(ctx, 3350, GSM0408_T3350_SECS);
+#endif
+		ctx->mm_state = GMM_REGISTERED_NORMAL;
+		return gsm48_tx_gmm_att_ack(ctx);
+	} 
+	if (!strlen(ctx->imei)) {
+		ctx->mm_state = GMM_COMMON_PROC_INIT;
+		ctx->t3370_id_type = GSM_MI_TYPE_IMEI;
+		mmctx_timer_start(ctx, 3370, GSM0408_T3370_SECS);
+		return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMEI);
+	}
+
+	if (!strlen(ctx->imsi)) {
+		ctx->mm_state = GMM_COMMON_PROC_INIT;
+		ctx->t3370_id_type = GSM_MI_TYPE_IMSI;
+		mmctx_timer_start(ctx, 3370, GSM0408_T3370_SECS);
+		return gsm48_tx_gmm_id_req(ctx, GSM_MI_TYPE_IMSI);
+	}
+
+	return 0;
+}
+
+/* Parse Chapter 9.4.13 Identity Response */
+static int gsm48_rx_gmm_id_resp(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+	DEBUGP(DMM, "-> GMM IDENTITY RESPONSE: mi_type=0x%02x MI(%s) ",
+		mi_type, mi_string);
+
+	if (!ctx) {
+		DEBUGP(DMM, "from unknown TLLI 0x%08x?!?\n", msgb_tlli(msg));
+		return -EINVAL;
+	}
+
+	if (mi_type == ctx->t3370_id_type)
+		mmctx_timer_stop(ctx, 3370);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* we already have a mm context with current TLLI, but no
+		 * P-TMSI / IMSI yet.  What we now need to do is to fill
+		 * this initial context with data from the HLR */
+		if (strlen(ctx->imsi) == 0) {
+			/* Check if we already have a MM context for this IMSI */
+			struct sgsn_mm_ctx *ictx;
+			ictx = sgsn_mm_ctx_by_imsi(mi_string);
+			if (ictx) {
+				DEBUGP(DMM, "Deleting old MM Context for same IMSI ",
+				       "p_tmsi_old=0x%08x, p_tmsi_new=0x%08x\n",
+					ictx->p_tmsi, ctx->p_tmsi);
+				gprs_llgmm_assign(ictx->llme, ictx->tlli,
+						  0xffffffff, GPRS_ALGO_GEA0, NULL);
+				sgsn_mm_ctx_free(ictx);
+			}
+		}
+		strncpy(ctx->imsi, mi_string, sizeof(ctx->imei));
+		break;
+	case GSM_MI_TYPE_IMEI:
+		strncpy(ctx->imei, mi_string, sizeof(ctx->imei));
+		break;
+	case GSM_MI_TYPE_IMEISV:
+		break;
+	}
+
+	DEBUGPC(DMM, "\n");
+	/* Check if we can let the mobile station enter */
+	return gsm48_gmm_authorize(ctx, ctx->t3350_mode);
+}
+
+/* Section 9.4.1 Attach request */
+static int gsm48_rx_gmm_att_req(struct sgsn_mm_ctx *ctx, struct msgb *msg,
+				struct gprs_llc_llme *llme)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t *cur = gh->data, *msnc, *mi, *old_ra_info, *ms_ra_acc_cap;
+	uint8_t msnc_len, att_type, mi_len, mi_type, ms_ra_acc_cap_len;
+	uint16_t drx_par;
+	uint32_t tmsi;
+	char mi_string[GSM48_MI_SIZE];
+	struct gprs_ra_id ra_id;
+	uint16_t cid;
+
+	DEBUGP(DMM, "-> GMM ATTACH REQUEST ");
+
+	/* As per TS 04.08 Chapter 4.7.1.4, the attach request arrives either
+	 * with a foreign TLLI (P-TMSI that was allocated to the MS before),
+	 * or with random TLLI. */
+
+	cid = bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+
+	/* MS network capability 10.5.5.12 */
+	msnc_len = *cur++;
+	msnc = cur;
+	if (msnc_len > 8)
+		goto err_inval;
+	cur += msnc_len;
+
+	/* aTTACH Type 10.5.5.2 */
+	att_type = *cur++ & 0x0f;
+
+	/* DRX parameter 10.5.5.6 */
+	drx_par = *cur++ << 8;
+	drx_par |= *cur++;
+
+	/* Mobile Identity (P-TMSI or IMSI) 10.5.1.4 */
+	mi_len = *cur++;
+	mi = cur;
+	if (mi_len > 8)
+		goto err_inval;
+	mi_type = *mi & GSM_MI_TYPE_MASK;
+	cur += mi_len;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+
+	DEBUGPC(DMM, "MI(%s) type=\"%s\" ", mi_string,
+		get_value_string(gprs_att_t_strs, att_type));
+
+	/* Old routing area identification 10.5.5.15 */
+	old_ra_info = cur;
+	cur += 6;
+
+	/* MS Radio Access Capability 10.5.5.12a */
+	ms_ra_acc_cap_len = *cur++;
+	ms_ra_acc_cap = cur;
+	if (ms_ra_acc_cap_len > 51)
+		goto err_inval;
+
+	/* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status */
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* Try to find MM context based on IMSI */
+		if (!ctx)
+			ctx = sgsn_mm_ctx_by_imsi(mi_string);
+		if (!ctx) {
+#if 0
+			return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_IMSI_UNKNOWN);
+#else
+			/* As a temorary hack, we simply assume that the IMSI exists,
+			 * as long as it is part of 'our' network */
+			char mccmnc[16];
+			snprintf(mccmnc, sizeof(mccmnc), "%03d%02d", ra_id.mcc, ra_id.mnc);
+			if (strncmp(mccmnc, mi_string, 5)) {
+				LOGP(DMM, LOGL_INFO, "Rejecting ATTACH REQUESET IMSI=%s\n",
+				     mi_string);
+				return gsm48_tx_gmm_att_rej_oldmsg(msg,
+								GMM_CAUSE_GPRS_NOTALLOWED);
+			}
+			ctx = sgsn_mm_ctx_alloc(0, &ra_id);
+			if (!ctx)
+				return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_NET_FAIL);
+			strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi));
+#endif
+		}
+		ctx->tlli = msgb_tlli(msg);
+		ctx->llme = llme;
+		msgid2mmctx(ctx, msg);
+		break;
+	case GSM_MI_TYPE_TMSI:
+		memcpy(&tmsi, mi+1, 4);
+		tmsi = ntohl(tmsi);
+		/* Try to find MM context based on P-TMSI */
+		if (!ctx)
+			ctx = sgsn_mm_ctx_by_ptmsi(tmsi);
+		if (!ctx) {
+			/* Allocate a context as most of our code expects one.
+			 * Context will not have an IMSI ultil ID RESP is received */
+			ctx = sgsn_mm_ctx_alloc(msgb_tlli(msg), &ra_id);
+			ctx->p_tmsi = tmsi;
+		}
+		ctx->tlli = msgb_tlli(msg);
+		ctx->llme = llme;
+		msgid2mmctx(ctx, msg);
+		break;
+	default:
+		LOGP(DMM, LOGL_NOTICE, "Rejecting ATTACH REQUEST with "
+			"MI type %u\n", mi_type);
+		return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+	}
+	/* Update MM Context with currient RA and Cell ID */
+	ctx->ra = ra_id;
+	ctx->cell_id = cid;
+	/* Update MM Context with other data */
+	ctx->drx_parms = drx_par;
+	ctx->ms_radio_access_capa.len = ms_ra_acc_cap_len;
+	memcpy(ctx->ms_radio_access_capa.buf, ms_ra_acc_cap, ms_ra_acc_cap_len);
+	ctx->ms_network_capa.len = msnc_len;
+	memcpy(ctx->ms_network_capa.buf, msnc, msnc_len);
+
+#ifdef PTMSI_ALLOC
+	/* Allocate a new P-TMSI (+ P-TMSI signature) and update TLLI */
+	ctx->p_tmsi_old = ctx->p_tmsi;
+	ctx->p_tmsi = sgsn_alloc_ptmsi();
+#endif
+	/* Even if there is no P-TMSI allocated, the MS will switch from
+	 * foreign TLLI to local TLLI */
+	ctx->tlli_new = gprs_tmsi2tlli(ctx->p_tmsi, TLLI_LOCAL);
+
+	/* Inform LLC layer about new TLLI but keep old active */
+	gprs_llgmm_assign(ctx->llme, ctx->tlli, ctx->tlli_new,
+			  GPRS_ALGO_GEA0, NULL);
+
+	DEBUGPC(DMM, "\n");
+	return ctx ? gsm48_gmm_authorize(ctx, GMM_T3350_MODE_ATT) : 0;
+
+err_inval:
+	DEBUGPC(DMM, "\n");
+	return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_SEM_INCORR_MSG);
+}
+
+/* Section 4.7.4.1 / 9.4.5.2 MO Detach request */
+static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct sgsn_pdp_ctx *pdp, *pdp2;
+	uint8_t detach_type, power_off;
+	int rc;
+
+	detach_type = gh->data[0] & 0x7;
+	power_off = gh->data[0] & 0x8;
+
+	/* FIXME: In 24.008 there is an optional P-TMSI and P-TMSI signature IE */
+
+	DEBUGP(DMM, "-> GMM DETACH REQUEST TLLI=0x%08x type=%s %s\n",
+		msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type),
+		power_off ? "Power-off" : "");
+
+	/* Mark MM state as deregistered */
+	ctx->mm_state = GMM_DEREGISTERED;
+
+	/* delete all existing PDP contexts for this MS */
+	llist_for_each_entry_safe(pdp, pdp2, &ctx->pdp_list, list) {
+		LOGP(DMM, LOGL_NOTICE, "Dropping PDP context for NSAPI=%u "
+		     "due to GPRS DETACH REQUEST\n", pdp->nsapi);
+		sgsn_delete_pdp_ctx(pdp);
+		/* FIXME: the callback wants to transmit a DEACT PDP CTX ACK,
+		 * which is quite stupid for a MS that has just detached.. */
+	}
+
+	/* force_stby = 0 */
+	rc = gsm48_tx_gmm_det_ack(ctx, 0);
+
+	/* TLLI unassignment */
+	gprs_llgmm_assign(ctx->llme, ctx->tlli, 0xffffffff,
+			  GPRS_ALGO_GEA0, NULL);
+
+	return rc;
+}
+
+/* Chapter 9.4.15: Routing area update accept */
+static int gsm48_tx_gmm_ra_upd_ack(struct sgsn_mm_ctx *mm)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_ra_upd_ack *rua;
+	uint8_t *mid;
+
+	DEBUGP(DMM, "<- ROUTING AREA UPDATE ACCEPT\n");
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_RA_UPD_ACK;
+
+	rua = (struct gsm48_ra_upd_ack *) msgb_put(msg, sizeof(*rua));
+	rua->force_stby = 0;	/* not indicated */
+	rua->upd_result = 0;	/* RA updated */
+	rua->ra_upd_timer = GPRS_TMR_MINUTE | 10;
+
+	gsm48_construct_ra(rua->ra_id.digits, &mm->ra);
+
+#if 0
+	/* Optional: P-TMSI signature */
+	msgb_v_put(msg, GSM48_IE_GMM_PTMSI_SIG);
+	ptsig = msgb_put(msg, 3);
+	ptsig[0] = mm->p_tmsi_sig >> 16;
+	ptsig[1] = mm->p_tmsi_sig >> 8;
+	ptsig[2] = mm->p_tmsi_sig & 0xff;
+#endif
+
+#ifdef PTMSI_ALLOC
+	/* Optional: Allocated P-TMSI */
+	mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+	gsm48_generate_mid_from_tmsi(mid, mm->p_tmsi);
+	mid[0] = GSM48_IE_GMM_ALLOC_PTMSI;
+#endif
+
+	/* Option: MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Chapter 9.4.17: Routing area update reject */
+static int gsm48_tx_gmm_ra_upd_rej(struct msgb *old_msg, uint8_t cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	DEBUGP(DMM, "<- ROUTING AREA UPDATE REJECT\n");
+
+	gmm_copy_id(msg, old_msg);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_RA_UPD_REJ;
+	gh->data[0] = cause;
+	gh->data[1] = 0; /* ? */
+
+	/* Option: P-TMSI signature, allocated P-TMSI, MS ID, ... */
+	return gsm48_gmm_sendmsg(msg, 0, NULL);
+}
+
+static void process_ms_ctx_status(struct sgsn_mm_ctx *mmctx,
+				  uint16_t pdp_status)
+{
+	struct sgsn_pdp_ctx *pdp, *pdp2;
+	/* 24.008 4.7.5.1.3: If the PDP context status information element is
+	 * included in ROUTING AREA UPDATE REQUEST message, then the network
+	 * shall deactivate all those PDP contexts locally (without peer to
+	 * peer signalling between the MS and the network), which are not in SM
+	 * state PDP-INACTIVE on network side but are indicated by the MS as
+	 * being in state PDP-INACTIVE. */
+
+	llist_for_each_entry_safe(pdp, pdp2, &mmctx->pdp_list, list) {
+		if (!(pdp_status & (1 << pdp->nsapi))) {
+			LOGP(DMM, LOGL_NOTICE, "Dropping PDP context for NSAPI=%u "
+				"due to PDP CTX STATUS IE= 0x%04x\n",
+				pdp->nsapi, pdp_status);
+			sgsn_delete_pdp_ctx(pdp);
+		}
+	}
+}
+
+/* Chapter 9.4.14: Routing area update request */
+static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
+				   struct gprs_llc_llme *llme)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t *cur = gh->data;
+	uint8_t *ms_ra_acc_cap;
+	uint8_t ms_ra_acc_cap_len;
+	struct gprs_ra_id old_ra_id;
+	struct tlv_parsed tp;
+	uint8_t upd_type;
+	int rc;
+
+	/* Update Type 10.5.5.18 */
+	upd_type = *cur++ & 0x0f;
+
+	DEBUGP(DMM, "-> GMM RA UPDATE REQUEST type=\"%s\" ",
+		get_value_string(gprs_upd_t_strs, upd_type));
+
+	/* Old routing area identification 10.5.5.15 */
+	gsm48_parse_ra(&old_ra_id, cur);
+	cur += 6;
+
+	/* MS Radio Access Capability 10.5.5.12a */
+	ms_ra_acc_cap_len = *cur++;
+	ms_ra_acc_cap = cur;
+
+	/* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status,
+	 * DRX parameter, MS network capability */
+	rc = tlv_parse(&tp, &gsm48_gmm_att_tlvdef, cur,
+			(msg->data + msg->len) - cur, 0, 0);
+
+	switch (upd_type) {
+	case GPRS_UPD_T_RA_LA:
+	case GPRS_UPD_T_RA_LA_IMSI_ATT:
+		DEBUGPC(DMM, " unsupported in Mode III, is your SI13 corrupt?\n");
+		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_PROTO_ERR_UNSPEC);
+		break;
+	case GPRS_UPD_T_RA:
+	case GPRS_UPD_T_PERIODIC:
+		break;
+	}
+
+	/* Look-up the MM context based on old RA-ID and TLLI */
+	mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &old_ra_id);
+	if (!mmctx || mmctx->mm_state == GMM_DEREGISTERED) {
+		/* The MS has to perform GPRS attach */
+		DEBUGPC(DMM, " REJECT\n");
+		/* Device is still IMSI atached for CS but initiate GPRS ATTACH */
+		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+	}
+
+	/* Store new BVCI/NSEI in MM context (FIXME: delay until we ack?) */
+	msgid2mmctx(mmctx, msg);
+	/* Bump the statistics of received signalling msgs for this MM context */
+	rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]);
+
+	/* Update the MM context with the new RA-ID */
+	bssgp_parse_cell_id(&mmctx->ra, msgb_bcid(msg));
+	/* Update the MM context with the new (i.e. foreign) TLLI */
+	mmctx->tlli = msgb_tlli(msg);
+	/* FIXME: Update the MM context with the MS radio acc capabilities */
+	/* FIXME: Update the MM context with the MS network capabilities */
+
+	rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_RA_UPDATE]);
+
+	DEBUGPC(DMM, " ACCEPT\n");
+#ifdef PTMSI_ALLOC
+	mmctx->p_tmsi_old = mmctx->p_tmsi;
+	mmctx->p_tmsi = sgsn_alloc_ptmsi();
+	/* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */
+	mmctx->t3350_mode = GMM_T3350_MODE_RAU;
+	mmctx_timer_start(mmctx, 3350, GSM0408_T3350_SECS);
+#endif
+	/* Even if there is no P-TMSI allocated, the MS will switch from
+	 * foreign TLLI to local TLLI */
+	mmctx->tlli_new = gprs_tmsi2tlli(mmctx->p_tmsi, TLLI_LOCAL);
+
+	/* Inform LLC layer about new TLLI but keep old active */
+	gprs_llgmm_assign(mmctx->llme, mmctx->tlli, mmctx->tlli_new,
+			  GPRS_ALGO_GEA0, NULL);
+
+	/* Look at PDP Context Status IE and see if MS's view of
+	 * activated/deactivated NSAPIs agrees with our view */
+	if (TLVP_PRESENT(&tp, GSM48_IE_GMM_PDP_CTX_STATUS)) {
+		uint16_t pdp_status = ntohs(*(uint16_t *)
+				TLVP_VAL(&tp, GSM48_IE_GMM_PDP_CTX_STATUS));
+		process_ms_ctx_status(mmctx, pdp_status);
+	}
+
+	/* Send RA UPDATE ACCEPT */
+	return gsm48_tx_gmm_ra_upd_ack(mmctx);
+}
+
+static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "-> GPRS MM STATUS (cause: %s)\n",
+		get_value_string(gmm_cause_names, gh->data[0]));
+
+	return 0;
+}
+
+/* GPRS Mobility Management */
+static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
+			   struct gprs_llc_llme *llme)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	int rc;
+
+	/* MMCTX can be NULL when called */
+
+	if (!mmctx &&
+	    gh->msg_type != GSM48_MT_GMM_ATTACH_REQ &&
+	    gh->msg_type != GSM48_MT_GMM_RA_UPD_REQ) {
+		LOGP(DMM, LOGL_NOTICE, "Cannot handle GMM for unknown MM CTX\n");
+		return gsm48_tx_gmm_status_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+	}
+
+	switch (gh->msg_type) {
+	case GSM48_MT_GMM_RA_UPD_REQ:
+		rc = gsm48_rx_gmm_ra_upd_req(mmctx, msg, llme);
+		break;
+	case GSM48_MT_GMM_ATTACH_REQ:
+		rc = gsm48_rx_gmm_att_req(mmctx, msg, llme);
+		break;
+	case GSM48_MT_GMM_ID_RESP:
+		rc = gsm48_rx_gmm_id_resp(mmctx, msg);
+		break;
+	case GSM48_MT_GMM_STATUS:
+		rc = gsm48_rx_gmm_status(mmctx, msg);
+		break;
+	case GSM48_MT_GMM_DETACH_REQ:
+		rc = gsm48_rx_gmm_det_req(mmctx, msg);
+		break;
+	case GSM48_MT_GMM_ATTACH_COMPL:
+		/* only in case SGSN offered new P-TMSI */
+		DEBUGP(DMM, "-> ATTACH COMPLETE\n");
+		mmctx_timer_stop(mmctx, 3350);
+		mmctx->p_tmsi_old = 0;
+		/* Unassign the old TLLI */
+		mmctx->tlli = mmctx->tlli_new;
+		gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new,
+				  GPRS_ALGO_GEA0, NULL);
+		break;
+	case GSM48_MT_GMM_RA_UPD_COMPL:
+		/* only in case SGSN offered new P-TMSI */
+		DEBUGP(DMM, "-> ROUTEING AREA UPDATE COMPLETE\n");
+		mmctx_timer_stop(mmctx, 3350);
+		mmctx->p_tmsi_old = 0;
+		/* Unassign the old TLLI */
+		mmctx->tlli = mmctx->tlli_new;
+		gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new,
+				  GPRS_ALGO_GEA0, NULL);
+		break;
+	case GSM48_MT_GMM_PTMSI_REALL_COMPL:
+		DEBUGP(DMM, "-> PTMSI REALLLICATION COMPLETE\n");
+		mmctx_timer_stop(mmctx, 3350);
+		mmctx->p_tmsi_old = 0;
+		/* Unassign the old TLLI */
+		mmctx->tlli = mmctx->tlli_new;
+		//gprs_llgmm_assign(mmctx->llme, 0xffffffff, mmctx->tlli_new, GPRS_ALGO_GEA0, NULL);
+		break;
+	case GSM48_MT_GMM_AUTH_CIPH_RESP:
+		rc = gsm48_rx_gmm_auth_ciph_resp(mmctx, msg);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 GMM msg type 0x%02x\n",
+			gh->msg_type);
+		rc = gsm48_tx_gmm_status(mmctx, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+		break;
+	}
+
+	return rc;
+}
+
+static void mmctx_timer_cb(void *_mm)
+{
+	struct sgsn_mm_ctx *mm = _mm;
+
+	mm->num_T_exp++;
+
+	switch (mm->T) {
+	case 3350:	/* waiting for ATTACH COMPLETE */
+		if (mm->num_T_exp >= 5) {
+			LOGP(DMM, LOGL_NOTICE, "T3350 expired >= 5 times\n");
+			mm->mm_state = GMM_DEREGISTERED;
+			/* FIXME: should we return some error? */
+			break;
+		}
+		/* re-transmit the respective msg and re-start timer */
+		switch (mm->t3350_mode) {
+		case GMM_T3350_MODE_ATT:
+			gsm48_tx_gmm_att_ack(mm);
+			break;
+		case GMM_T3350_MODE_RAU:
+			gsm48_tx_gmm_ra_upd_ack(mm);
+			break;
+		case GMM_T3350_MODE_PTMSI_REALL:
+			/* FIXME */
+			break;
+		}
+		bsc_schedule_timer(&mm->timer, GSM0408_T3350_SECS, 0);
+		break;
+	case 3360:	/* waiting for AUTH AND CIPH RESP */
+		if (mm->num_T_exp >= 5) {
+			LOGP(DMM, LOGL_NOTICE, "T3360 expired >= 5 times\n");
+			mm->mm_state = GMM_DEREGISTERED;
+			break;
+		}
+		/* FIXME: re-transmit the respective msg and re-start timer */
+		bsc_schedule_timer(&mm->timer, GSM0408_T3360_SECS, 0);
+		break;
+	case 3370:	/* waiting for IDENTITY RESPONSE */
+		if (mm->num_T_exp >= 5) {
+			LOGP(DMM, LOGL_NOTICE, "T3370 expired >= 5 times\n");
+			gsm48_tx_gmm_att_rej(mm, GMM_CAUSE_MS_ID_NOT_DERIVED);
+			mm->mm_state = GMM_DEREGISTERED;
+			break;
+		}
+		/* re-tranmit IDENTITY REQUEST and re-start timer */
+		gsm48_tx_gmm_id_req(mm, mm->t3370_id_type);
+		bsc_schedule_timer(&mm->timer, GSM0408_T3370_SECS, 0);
+		break;
+	default:
+		LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n",
+			mm->T);
+	}
+}
+
+/* GPRS SESSION MANAGEMENT */
+
+static void pdpctx_timer_cb(void *_mm);
+
+static void pdpctx_timer_start(struct sgsn_pdp_ctx *pdp, unsigned int T,
+				unsigned int seconds)
+{
+	if (bsc_timer_pending(&pdp->timer))
+		LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old "
+			"timer %u pending\n", T, pdp->T);
+	pdp->T = T;
+	pdp->num_T_exp = 0;
+
+	/* FIXME: we should do this only once ? */
+	pdp->timer.data = pdp;
+	pdp->timer.cb = &pdpctx_timer_cb;
+
+	bsc_schedule_timer(&pdp->timer, seconds, 0);
+}
+
+
+static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr)
+{
+	uint8_t v[6];
+
+	v[0] = PDP_TYPE_ORG_IETF;
+	v[1] = PDP_TYPE_N_IETF_IPv4;
+	*(uint32_t *)(v+2) = htonl(ipaddr);
+
+	msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v);
+}
+
+static void msgb_put_pdp_addr_ppp(struct msgb *msg)
+{
+	uint8_t v[2];
+
+	v[0] = PDP_TYPE_ORG_ETSI;
+	v[1] = PDP_TYPE_N_ETSI_PPP;
+
+	msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR, sizeof(v), v);
+}
+
+/* Section 9.5.2: Ativate PDP Context Accept */
+int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = pdp->ti ^ 0x8; /* flip */
+
+	DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT ACK\n");
+
+	mmctx2msgid(msg, pdp->mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+	gh->msg_type = GSM48_MT_GSM_ACT_PDP_ACK;
+
+	/* Negotiated LLC SAPI */
+	msgb_v_put(msg, pdp->sapi);
+
+	/* FIXME: copy QoS parameters from original request */
+	//msgb_lv_put(msg, pdp->lib->qos_neg.l, pdp->lib->qos_neg.v);
+	msgb_lv_put(msg, sizeof(default_qos), (uint8_t *)&default_qos);
+
+	/* Radio priority 10.5.7.2 */
+	msgb_v_put(msg, pdp->lib->radio_pri);
+
+	/* PDP address */
+	/* Highest 4 bits of first byte need to be set to 1, otherwise
+	 * the IE is identical with the 04.08 PDP Address IE */
+	pdp->lib->eua.v[0] &= ~0xf0;
+	msgb_tlv_put(msg, GSM48_IE_GSM_PDP_ADDR,
+		     pdp->lib->eua.l, pdp->lib->eua.v);
+	pdp->lib->eua.v[0] |= 0xf0;
+
+	/* Optional: Protocol configuration options (FIXME: why 'req') */
+	if (pdp->lib->pco_req.l && pdp->lib->pco_req.v)
+		msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT,
+			     pdp->lib->pco_req.l, pdp->lib->pco_req.v);
+
+	/* Optional: Packet Flow Identifier */
+
+	return gsm48_gmm_sendmsg(msg, 0, pdp->mm);
+}
+
+/* Section 9.5.3: Activate PDP Context reject */
+int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid,
+			     uint8_t cause, uint8_t pco_len, uint8_t *pco_v)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+	DEBUGP(DMM, "<- ACTIVATE PDP CONTEXT REJ(cause=%u)\n", cause);
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+	gh->msg_type = GSM48_MT_GSM_ACT_PDP_REJ;
+
+	msgb_v_put(msg, cause);
+	if (pco_len && pco_v)
+		msgb_tlv_put(msg, GSM48_IE_GSM_PROTO_CONF_OPT, pco_len, pco_v);
+
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+
+/* Section 9.5.8: Deactivate PDP Context Request */
+static int _gsm48_tx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, uint8_t tid,
+					uint8_t sm_cause)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+	DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT REQ\n");
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+	gh->msg_type = GSM48_MT_GSM_DEACT_PDP_REQ;
+
+	msgb_v_put(msg, sm_cause);
+
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause)
+{
+	pdpctx_timer_start(pdp, 3395, GSM0408_T3395_SECS);
+
+	return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause);
+}
+
+/* Section 9.5.9: Deactivate PDP Context Accept */
+static int _gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mm, uint8_t tid)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	uint8_t transaction_id = tid ^ 0x8; /* flip */
+
+	DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT ACK\n");
+
+	mmctx2msgid(msg, mm);
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4);
+	gh->msg_type = GSM48_MT_GSM_DEACT_PDP_ACK;
+
+	return gsm48_gmm_sendmsg(msg, 0, mm);
+}
+int gsm48_tx_gsm_deact_pdp_acc(struct sgsn_pdp_ctx *pdp)
+{
+	return _gsm48_tx_gsm_deact_pdp_acc(pdp->mm, pdp->ti);
+}
+
+/* Section 9.5.1: Activate PDP Context Request */
+static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx,
+				    struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	struct gsm48_act_pdp_ctx_req *act_req = (struct gsm48_act_pdp_ctx_req *) gh->data;
+	uint8_t req_qos_len, req_pdpa_len;
+	uint8_t *req_qos, *req_pdpa;
+	struct tlv_parsed tp;
+	uint8_t transaction_id = (gh->proto_discr >> 4);
+	struct sgsn_ggsn_ctx *ggsn;
+	struct sgsn_pdp_ctx *pdp;
+
+	DEBUGP(DMM, "-> ACTIVATE PDP CONTEXT REQ: SAPI=%u NSAPI=%u ",
+		act_req->req_llc_sapi, act_req->req_nsapi);
+
+	/* FIXME: length checks! */
+	req_qos_len = act_req->data[0];
+	req_qos = act_req->data + 1;	/* 10.5.6.5 */
+	req_pdpa_len = act_req->data[1 + req_qos_len];
+	req_pdpa = act_req->data + 1 + req_qos_len + 1;	/* 10.5.6.4 */
+
+	/* Optional: Access Point Name, Protocol Config Options */
+	if (req_pdpa + req_pdpa_len < msg->data + msg->len)
+		tlv_parse(&tp, &gsm48_sm_att_tlvdef, req_pdpa + req_pdpa_len,
+			  (msg->data + msg->len) - (req_pdpa + req_pdpa_len), 0, 0);
+	else
+		memset(&tp, 0, sizeof(tp));
+
+	switch (req_pdpa[0] & 0xf) {
+	case 0x0:
+		DEBUGPC(DMM, "ETSI ");
+		break;
+	case 0x1:
+		DEBUGPC(DMM, "IETF ");
+		break;
+	case 0xf:
+		DEBUGPC(DMM, "Empty ");
+		break;
+	}
+
+	switch (req_pdpa[1]) {
+	case 0x21:
+		DEBUGPC(DMM, "IPv4 ");
+		if (req_pdpa_len >= 6) {
+			struct in_addr ia;
+			ia.s_addr = ntohl(*((uint32_t *) (req_pdpa+2)));
+			DEBUGPC(DMM, "%s ", inet_ntoa(ia));
+		}
+		break;
+	case 0x57:
+		DEBUGPC(DMM, "IPv6 ");
+		if (req_pdpa_len >= 18) {
+			/* FIXME: print IPv6 address */
+		}
+		break;
+	default:	
+		DEBUGPC(DMM, "0x%02x ", req_pdpa[1]);
+		break;
+	}
+
+	DEBUGPC(DMM, "\n");
+
+	/* put the non-TLV elements in the TLV parser structure to
+	 * pass them on to the SGSN / GTP code */
+	tp.lv[OSMO_IE_GSM_REQ_QOS].len = req_qos_len;
+	tp.lv[OSMO_IE_GSM_REQ_QOS].val = req_qos;
+	tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].len = req_pdpa_len;
+	tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].val = req_pdpa;
+
+	/* FIXME:  determine GGSN based on APN and subscription options */
+	if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {}
+
+	/* Check if NSAPI is out of range (TS 04.65 / 7.2) */
+	if (act_req->req_nsapi < 5 || act_req->req_nsapi > 15) {
+		/* Send reject with GSM_CAUSE_INV_MAND_INFO */
+		return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id,
+						GSM_CAUSE_INV_MAND_INFO,
+						0, NULL);
+	}
+
+	/* Check if NSAPI is already in use */
+	pdp = sgsn_pdp_ctx_by_nsapi(mmctx, act_req->req_nsapi);
+	if (pdp) {
+		/* We already have a PDP context for this TLLI + NSAPI tuple */
+		if (pdp->sapi == act_req->req_llc_sapi &&
+		    pdp->ti == transaction_id) {
+			/* This apparently is a re-transmission of a PDP CTX
+			 * ACT REQ (our ACT ACK must have got dropped) */
+			return gsm48_tx_gsm_act_pdp_acc(pdp);
+		}
+
+		/* Send reject with GSM_CAUSE_NSAPI_IN_USE */
+		return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id,
+						GSM_CAUSE_NSAPI_IN_USE,
+						0, NULL);
+	}
+
+	/* Only increment counter for a real activation, after we checked
+	 * for re-transmissions */
+	rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PDP_CTX_ACT]);
+
+	ggsn = sgsn_ggsn_ctx_by_id(0);
+	if (!ggsn) {
+		LOGP(DGPRS, LOGL_ERROR, "No GGSN context 0 found!\n");
+		return -EIO;
+	}
+	ggsn->gsn = sgsn->gsn;
+	pdp = sgsn_create_pdp_ctx(ggsn, mmctx, act_req->req_nsapi, &tp);
+	if (!pdp)
+		return -1;
+
+	/* Store SAPI and Transaction Identifier */
+	pdp->sapi = act_req->req_llc_sapi;
+	pdp->ti = transaction_id;
+
+	return 0;
+}
+
+/* Section 9.5.8: Deactivate PDP Context Request */
+static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t transaction_id = (gh->proto_discr >> 4);
+	struct sgsn_pdp_ctx *pdp;
+
+	DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT REQ (cause: %s)\n",
+		get_value_string(gsm_cause_names, gh->data[0]));
+
+	pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id);
+	if (!pdp) {
+		LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Request for "
+			"non-existing PDP Context (IMSI=%s, TI=%u)\n",
+			mm->imsi, transaction_id);
+		return _gsm48_tx_gsm_deact_pdp_acc(mm, transaction_id);
+	}
+
+	return sgsn_delete_pdp_ctx(pdp);
+}
+
+/* Section 9.5.9: Deactivate PDP Context Accept */
+static int gsm48_rx_gsm_deact_pdp_ack(struct sgsn_mm_ctx *mm, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t transaction_id = (gh->proto_discr >> 4);
+	struct sgsn_pdp_ctx *pdp;
+
+	DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT ACK\n");
+
+	pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id);
+	if (!pdp) {
+		LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Accept for "
+			"non-existing PDP Context (IMSI=%s, TI=%u)\n",
+			mm->imsi, transaction_id);
+		return 0;
+	}
+
+	return sgsn_delete_pdp_ctx(pdp);
+}
+
+static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "-> GPRS SM STATUS (cause: %s)\n",
+		get_value_string(gsm_cause_names, gh->data[0]));
+
+	return 0;
+}
+
+static void pdpctx_timer_cb(void *_pdp)
+{
+	struct sgsn_pdp_ctx *pdp = _pdp;
+
+	pdp->num_T_exp++;
+
+	switch (pdp->T) {
+	case 3395:	/* waiting for PDP CTX DEACT ACK */
+		if (pdp->num_T_exp >= 4) {
+			LOGP(DMM, LOGL_NOTICE, "T3395 expired >= 5 times\n");
+			pdp->state = PDP_STATE_INACTIVE;
+			sgsn_delete_pdp_ctx(pdp);
+			break;
+		}
+		gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL); 
+		bsc_schedule_timer(&pdp->timer, GSM0408_T3395_SECS, 0);
+		break;
+	default:
+		LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n",
+			pdp->T);
+	}
+}
+
+
+/* GPRS Session Management */
+static int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg,
+			   struct gprs_llc_llme *llme)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	int rc;
+
+	/* MMCTX can be NULL when called */
+
+	if (!mmctx) {
+		LOGP(DMM, LOGL_NOTICE, "Cannot handle SM for unknown MM CTX\n");
+		gsm48_tx_gmm_status_oldmsg(msg, GMM_CAUSE_IMPL_DETACHED);
+		return gsm48_tx_sm_status_oldmsg(msg, GSM_CAUSE_PROTO_ERR_UNSPEC);
+	}
+
+	switch (gh->msg_type) {
+	case GSM48_MT_GSM_ACT_PDP_REQ:
+		rc = gsm48_rx_gsm_act_pdp_req(mmctx, msg);
+		break;
+	case GSM48_MT_GSM_DEACT_PDP_REQ:
+		rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg);
+		break;
+	case GSM48_MT_GSM_DEACT_PDP_ACK:
+		rc = gsm48_rx_gsm_deact_pdp_ack(mmctx, msg);
+		break;
+	case GSM48_MT_GSM_STATUS:
+		rc = gsm48_rx_gsm_status(mmctx, msg);
+		break;
+	case GSM48_MT_GSM_REQ_PDP_ACT_REJ:
+	case GSM48_MT_GSM_ACT_AA_PDP_REQ:
+	case GSM48_MT_GSM_DEACT_AA_PDP_REQ:
+		DEBUGP(DMM, "Unimplemented GSM 04.08 GSM msg type 0x%02x\n",
+			gh->msg_type);
+		rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 GSM msg type 0x%02x\n",
+			gh->msg_type);
+		rc = gsm48_tx_sm_status(mmctx, GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL);
+		break;
+
+	}
+
+	return rc;
+}
+
+/* Main entry point for incoming 04.08 GPRS messages */
+int gsm0408_gprs_rcvmsg(struct msgb *msg, struct gprs_llc_llme *llme)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
+	uint8_t pdisc = gh->proto_discr & 0x0f;
+	struct sgsn_mm_ctx *mmctx;
+	struct gprs_ra_id ra_id;
+	int rc = -EINVAL;
+
+	bssgp_parse_cell_id(&ra_id, msgb_bcid(msg));
+	mmctx = sgsn_mm_ctx_by_tlli(msgb_tlli(msg), &ra_id);
+	if (mmctx) {
+		msgid2mmctx(mmctx, msg);
+		rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_SIG_IN]);
+		mmctx->llme = llme;
+	}
+
+	/* MMCTX can be NULL */
+
+	switch (pdisc) {
+	case GSM48_PDISC_MM_GPRS:
+		rc = gsm0408_rcv_gmm(mmctx, msg, llme);
+		break;
+	case GSM48_PDISC_SM_GPRS:
+		rc = gsm0408_rcv_gsm(mmctx, msg, llme);
+		break;
+	default:
+		DEBUGP(DMM, "Unknown GSM 04.08 discriminator 0x%02x\n",
+			pdisc);
+		/* FIXME: return status message */
+		break;
+	}
+
+	return rc;
+}
+
+int gprs_gmm_rx_suspend(struct gprs_ra_id *raid, uint32_t tlli)
+{
+	struct sgsn_mm_ctx *mmctx;
+
+	mmctx = sgsn_mm_ctx_by_tlli(tlli, raid);
+	if (!mmctx) {
+		LOGP(DMM, LOGL_NOTICE, "SUSPEND request for unknown "
+			"TLLI=%08x\n", tlli);
+		return -EINVAL;
+	}
+
+	if (mmctx->mm_state != GMM_REGISTERED_NORMAL) {
+		LOGP(DMM, LOGL_NOTICE, "SUSPEND request while state "
+			"!= REGISTERED (TLLI=%08x)\n", tlli);
+		return -EINVAL;
+	}
+
+	/* Transition from REGISTERED_NORMAL to REGISTERED_SUSPENDED */
+	mmctx->mm_state = GMM_REGISTERED_SUSPENDED;
+	return 0;
+}
+
+int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli,
+		       uint8_t suspend_ref)
+{
+	struct sgsn_mm_ctx *mmctx;
+
+	/* FIXME: make use of suspend reference? */
+
+	mmctx = sgsn_mm_ctx_by_tlli(tlli, raid);
+	if (!mmctx) {
+		LOGP(DMM, LOGL_NOTICE, "RESUME request for unknown "
+			"TLLI=%08x\n", tlli);
+		return -EINVAL;
+	}
+
+	if (mmctx->mm_state != GMM_REGISTERED_SUSPENDED) {
+		LOGP(DMM, LOGL_NOTICE, "RESUME request while state "
+			"!= SUSPENDED (TLLI=%08x)\n", tlli);
+		/* FIXME: should we not simply ignore it? */
+		return -EINVAL;
+	}
+
+	/* Transition from SUSPENDED to NORMAL */
+	mmctx->mm_state = GMM_REGISTERED_NORMAL;
+	return 0;
+}
diff --git a/src/gprs/gprs_llc.c b/src/gprs/gprs_llc.c
new file mode 100644
index 0000000..7991f4c
--- /dev/null
+++ b/src/gprs/gprs_llc.c
@@ -0,0 +1,852 @@
+/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/timer.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/crc24.h>
+
+/* Section 8.9.9 LLC layer parameter default values */
+static const struct gprs_llc_params llc_default_params[] = {
+	[1] = {
+		.t200_201	= 5,
+		.n200		= 3,
+		.n201_u		= 400,
+	},
+	[2] = {
+		.t200_201	= 5,
+		.n200		= 3,
+		.n201_u		= 270,
+	},
+	[3] = {
+		.iov_i_exp	= 27,
+		.t200_201	= 5,
+		.n200		= 3,
+		.n201_u		= 500,
+		.n201_i		= 1503,
+		.mD		= 1520,
+		.mU		= 1520,
+		.kD		= 16,
+		.kU		= 16,
+	},
+	[5] = {
+		.iov_i_exp	= 27,
+		.t200_201	= 10,
+		.n200		= 3,
+		.n201_u		= 500,
+		.n201_i		= 1503,
+		.mD		= 760,
+		.mU		= 760,
+		.kD		= 8,
+		.kU		= 8,
+	},
+	[7] = {
+		.t200_201	= 20,
+		.n200		= 3,
+		.n201_u		= 270,
+	},
+	[8] = {
+		.t200_201	= 20,
+		.n200		= 3,
+		.n201_u		= 270,
+	},
+	[9] = {
+		.iov_i_exp	= 27,
+		.t200_201	= 20,
+		.n200		= 3,
+		.n201_u		= 500,
+		.n201_i		= 1503,
+		.mD		= 380,
+		.mU		= 380,
+		.kD		= 4,
+		.kU		= 4,
+	},
+	[11] = {
+		.iov_i_exp	= 27,
+		.t200_201	= 40,
+		.n200		= 3,
+		.n201_u		= 500,
+		.n201_i		= 1503,
+		.mD		= 190,
+		.mU		= 190,
+		.kD		= 2,
+		.kU		= 2,
+	},
+};
+
+LLIST_HEAD(gprs_llc_llmes);
+void *llc_tall_ctx;
+
+/* If the TLLI is foreign, return its local version */
+static inline uint32_t tlli_foreign2local(uint32_t tlli)
+{
+	uint32_t new_tlli;
+
+	if (gprs_tlli_type(tlli) == TLLI_FOREIGN) {
+		new_tlli = tlli | 0x40000000;
+		DEBUGP(DLLC, "TLLI 0x%08x is foreign, converting to "
+			"local TLLI 0x%08x\n", tlli, new_tlli);
+	} else
+		new_tlli = tlli;
+
+	return new_tlli;
+}
+
+/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */
+static struct gprs_llc_lle *lle_by_tlli_sapi(uint32_t tlli, uint8_t sapi)
+{
+	struct gprs_llc_llme *llme;
+
+	tlli = tlli_foreign2local(tlli);
+
+	llist_for_each_entry(llme, &gprs_llc_llmes, list) {
+		if (llme->tlli == tlli || llme->old_tlli == tlli)
+			return &llme->lle[sapi];
+	}
+	return NULL;
+}
+
+static void lle_init(struct gprs_llc_llme *llme, uint8_t sapi)
+{
+	struct gprs_llc_lle *lle = &llme->lle[sapi];
+
+	lle->llme = llme;
+	lle->sapi = sapi;
+	lle->state = GPRS_LLES_UNASSIGNED;
+
+	/* Initialize according to parameters */
+	memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params));
+}
+
+static struct gprs_llc_llme *llme_alloc(uint32_t tlli)
+{
+	struct gprs_llc_llme *llme;
+	uint32_t i;
+
+	llme = talloc_zero(llc_tall_ctx, struct gprs_llc_llme);
+	if (!llme)
+		return NULL;
+
+	llme->tlli = tlli;
+	llme->old_tlli = 0xffffffff;
+	llme->state = GPRS_LLMS_UNASSIGNED;
+
+	for (i = 0; i < ARRAY_SIZE(llme->lle); i++)
+		lle_init(llme, i);
+
+	llist_add(&llme->list, &gprs_llc_llmes);
+
+	return llme;
+}
+
+static void llme_free(struct gprs_llc_llme *llme)
+{
+	llist_del(&llme->list);
+	talloc_free(llme);
+}
+
+enum gprs_llc_cmd {
+	GPRS_LLC_NULL,
+	GPRS_LLC_RR,
+	GPRS_LLC_ACK,
+	GPRS_LLC_RNR,
+	GPRS_LLC_SACK,
+	GPRS_LLC_DM,
+	GPRS_LLC_DISC,
+	GPRS_LLC_UA,
+	GPRS_LLC_SABM,
+	GPRS_LLC_FRMR,
+	GPRS_LLC_XID,
+	GPRS_LLC_UI,
+};
+
+static const struct value_string llc_cmd_strs[] = {
+	{ GPRS_LLC_NULL,	"NULL" },
+	{ GPRS_LLC_RR,		"RR" },
+	{ GPRS_LLC_ACK,		"ACK" },
+	{ GPRS_LLC_RNR,		"RNR" },
+	{ GPRS_LLC_SACK,	"SACK" },
+	{ GPRS_LLC_DM,		"DM" },
+	{ GPRS_LLC_DISC,	"DISC" },
+	{ GPRS_LLC_UA,		"UA" },
+	{ GPRS_LLC_SABM,	"SABM" },
+	{ GPRS_LLC_FRMR,	"FRMR" },
+	{ GPRS_LLC_XID,		"XID" },
+	{ GPRS_LLC_UI,		"UI" },
+	{ 0, NULL }
+};
+
+struct gprs_llc_hdr_parsed {
+	uint8_t sapi;
+	uint8_t is_cmd:1,
+		 ack_req:1,
+		 is_encrypted:1;
+	uint32_t seq_rx;
+	uint32_t seq_tx;
+	uint32_t fcs;
+	uint32_t fcs_calc;
+	uint8_t *data;
+	uint16_t data_len;
+	uint16_t crc_length;
+	enum gprs_llc_cmd cmd;
+};
+
+#define LLC_ALLOC_SIZE 16384
+#define UI_HDR_LEN	3
+#define N202		4
+#define CRC24_LENGTH	3
+
+static int gprs_llc_fcs(uint8_t *data, unsigned int len)
+{
+	uint32_t fcs_calc;
+
+	fcs_calc = crc24_calc(INIT_CRC24, data, len);
+	fcs_calc = ~fcs_calc;
+	fcs_calc &= 0xffffff;
+
+	return fcs_calc;
+}
+
+static void t200_expired(void *data)
+{
+	struct gprs_llc_lle *lle = data;
+
+	/* 8.5.1.3: Expiry of T200 */
+
+	if (lle->retrans_ctr >= lle->params.n200) {
+		/* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */
+		lle->state = GPRS_LLES_ASSIGNED_ADM;
+	}
+
+	switch (lle->state) {
+	case GPRS_LLES_LOCAL_EST:
+		/* FIXME: retransmit SABM */
+		/* FIXME: re-start T200 */
+		lle->retrans_ctr++;
+		break;
+	case GPRS_LLES_LOCAL_REL:
+		/* FIXME: retransmit DISC */
+		/* FIXME: re-start T200 */
+		lle->retrans_ctr++;
+		break;
+	}
+
+}
+
+static void t201_expired(void *data)
+{
+	struct gprs_llc_lle *lle = data;
+
+	if (lle->retrans_ctr < lle->params.n200) {
+		/* FIXME: transmit apropriate supervisory frame (8.6.4.1) */
+		/* FIXME: set timer T201 */
+		lle->retrans_ctr++;
+	}
+}
+
+int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command,
+		  enum gprs_llc_u_cmd u_cmd, int pf_bit)
+{
+	uint8_t *fcs, *llch;
+	uint8_t addr, ctrl;
+	uint32_t fcs_calc;
+
+	/* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+	/* Address Field */
+	addr = sapi & 0xf;
+	if (command)
+		addr |= 0x40;
+
+	/* 6.3 Figure 8 */
+	ctrl = 0xe0 | u_cmd;
+	if (pf_bit)
+		ctrl |= 0x10;
+
+	/* prepend LLC UI header */
+	llch = msgb_push(msg, 2);
+	llch[0] = addr;
+	llch[1] = ctrl;
+
+	/* append FCS to end of frame */
+	fcs = msgb_put(msg, 3);
+	fcs_calc = gprs_llc_fcs(llch, fcs - llch);
+	fcs[0] = fcs_calc & 0xff;
+	fcs[1] = (fcs_calc >> 8) & 0xff;
+	fcs[2] = (fcs_calc >> 16) & 0xff;
+
+	/* Identifiers passed down: (BVCI, NSEI) */
+
+	/* Send BSSGP-DL-UNITDATA.req */
+	return gprs_bssgp_tx_dl_ud(msg, NULL);
+}
+
+/* Send XID response to LLE */
+static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg)
+{
+	/* copy identifiers from LLE to ensure lower layers can route */
+	msgb_tlli(msg) = lle->llme->tlli;
+	msgb_bvci(msg) = lle->llme->bvci;
+	msgb_nsei(msg) = lle->llme->nsei;
+
+	return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_XID, 1);
+}
+
+/* Transmit a UI frame over the given SAPI */
+int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command,
+		   void *mmctx)
+{
+	struct gprs_llc_lle *lle;
+	uint8_t *fcs, *llch;
+	uint8_t addr, ctrl[2];
+	uint32_t fcs_calc;
+	uint16_t nu = 0;
+	uint32_t oc;
+
+	/* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+	/* look-up or create the LL Entity for this (TLLI, SAPI) tuple */
+	lle = lle_by_tlli_sapi(msgb_tlli(msg), sapi);
+	if (!lle) {
+		struct gprs_llc_llme *llme;
+		LOGP(DLLC, LOGL_ERROR, "LLC TX: unknown TLLI 0x%08x, "
+			"creating LLME on the fly\n", msgb_tlli(msg));
+		llme = llme_alloc(msgb_tlli(msg));
+		lle = &llme->lle[sapi];
+	}
+
+	if (msg->len > lle->params.n201_u) {
+		LOGP(DLLC, LOGL_ERROR, "Cannot Tx %u bytes (N201-U=%u)\n",
+			msg->len, lle->params.n201_u);
+		return -EFBIG;
+	}
+
+	/* Update LLE's (BVCI, NSEI) tuple */
+	lle->llme->bvci = msgb_bvci(msg);
+	lle->llme->nsei = msgb_nsei(msg);
+
+	/* Obtain current values for N(u) and OC */
+	nu = lle->vu_send;
+	oc = lle->oc_ui_send;
+	/* Increment V(U) */
+	lle->vu_send = (lle->vu_send + 1) % 512;
+	/* Increment Overflow Counter, if needed */
+	if ((lle->vu_send + 1) / 512)
+		lle->oc_ui_send += 512;
+
+	/* Address Field */
+	addr = sapi & 0xf;
+	if (command)
+		addr |= 0x40;
+
+	/* Control Field */
+	ctrl[0] = 0xc0;
+	ctrl[0] |= nu >> 6;
+	ctrl[1] = (nu << 2) & 0xfc;
+	ctrl[1] |= 0x01; /* Protected Mode */
+
+	/* prepend LLC UI header */
+	llch = msgb_push(msg, 3);
+	llch[0] = addr;
+	llch[1] = ctrl[0];
+	llch[2] = ctrl[1];
+
+	/* append FCS to end of frame */
+	fcs = msgb_put(msg, 3);
+	fcs_calc = gprs_llc_fcs(llch, fcs - llch);
+	fcs[0] = fcs_calc & 0xff;
+	fcs[1] = (fcs_calc >> 8) & 0xff;
+	fcs[2] = (fcs_calc >> 16) & 0xff;
+
+	/* encrypt information field + FCS, if needed! */
+	if (lle->llme->algo != GPRS_ALGO_GEA0) {
+		uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */
+		uint16_t crypt_len = (fcs + 3) - (llch + 3);
+		uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK];
+		uint32_t iv;
+		int rc, i;
+		uint64_t kc = *(uint64_t *)&lle->llme->kc;
+
+		/* Compute the 'Input' Paraemeter */
+		iv = gprs_cipher_gen_input_ui(iov_ui, sapi, nu, oc);
+
+		/* Compute the keystream that we need to XOR with the data */
+		rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo,
+				     kc, iv, GPRS_CIPH_SGSN2MS);
+		if (rc < 0) {
+			LOGP(DLLC, LOGL_ERROR, "Error crypting UI frame: %d\n", rc);
+			return rc;
+		}
+
+		/* XOR the cipher output with the information field + FCS */
+		for (i = 0; i < crypt_len; i++)
+			*(llch + 3 + i) ^= cipher_out[i];
+
+		/* Mark frame as encrypted */
+		ctrl[1] |= 0x02;
+	}
+
+	/* Identifiers passed down: (BVCI, NSEI) */
+
+	/* Send BSSGP-DL-UNITDATA.req */
+	return gprs_bssgp_tx_dl_ud(msg, mmctx);
+}
+
+static void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph)
+{
+	DEBUGP(DLLC, "LLC SAPI=%u %c %c FCS=0x%06x",
+		gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ',
+		gph->fcs);
+
+	if (gph->cmd)
+		DEBUGPC(DLLC, "CMD=%s ", get_value_string(llc_cmd_strs, gph->cmd));
+
+	if (gph->data)
+		DEBUGPC(DLLC, "DATA ");
+
+	DEBUGPC(DLLC, "\n");
+}
+static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph,
+			   struct gprs_llc_lle *lle)
+{
+	switch (gph->cmd) {
+	case GPRS_LLC_SABM: /* Section 6.4.1.1 */
+		lle->v_sent = lle->v_ack = lle->v_recv = 0;
+		if (lle->state == GPRS_LLES_ASSIGNED_ADM) {
+			/* start re-establishment (8.7.1) */
+		}
+		lle->state = GPRS_LLES_REMOTE_EST;
+		/* FIXME: Send UA */
+		lle->state = GPRS_LLES_ABM;
+		/* FIXME: process data */
+		break;
+	case GPRS_LLC_DISC: /* Section 6.4.1.2 */
+		/* FIXME: Send UA */
+		/* terminate ABM */
+		lle->state = GPRS_LLES_ASSIGNED_ADM;
+		break;
+	case GPRS_LLC_UA: /* Section 6.4.1.3 */
+		if (lle->state == GPRS_LLES_LOCAL_EST)
+			lle->state = GPRS_LLES_ABM;
+		break;
+	case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */
+		if (lle->state == GPRS_LLES_LOCAL_EST)
+			lle->state = GPRS_LLES_ASSIGNED_ADM;
+		break;
+	case GPRS_LLC_FRMR: /* Section 6.4.1.5 */
+		break;
+	case GPRS_LLC_XID: /* Section 6.4.1.6 */
+		/* FIXME: implement XID negotiation using SNDCP */
+		{
+			struct msgb *resp;
+			uint8_t *xid;
+			resp = msgb_alloc_headroom(4096, 1024, "LLC_XID");
+			xid = msgb_put(resp, gph->data_len);
+			memcpy(xid, gph->data, gph->data_len);
+			gprs_llc_tx_xid(lle, resp);
+		}
+		break;
+	case GPRS_LLC_UI:
+		if (gph->seq_tx < lle->vu_recv) {
+			LOGP(DLLC, LOGL_NOTICE, "TLLI=%08x dropping UI, vurecv %u <= %u\n",
+				lle->llme ? lle->llme->tlli : -1,
+				gph->seq_tx, lle->vu_recv);
+			return -EIO;
+		}
+		/* Increment the sequence number that we expect in the next frame */
+		lle->vu_recv = (gph->seq_tx + 1) % 512;
+		/* Increment Overflow Counter */
+		if ((gph->seq_tx + 1) / 512)
+			lle->oc_ui_recv += 512;
+		break;
+	}
+
+	return 0;
+}
+
+/* parse a GPRS LLC header, also check for invalid frames */
+static int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp,
+			      uint8_t *llc_hdr, int len)
+{
+	uint8_t *ctrl = llc_hdr+1;
+	int is_sack = 0;
+
+	if (len <= CRC24_LENGTH)
+		return -EIO;
+
+	ghp->crc_length = len - CRC24_LENGTH;
+
+	ghp->ack_req = 0;
+
+	/* Section 5.5: FCS */
+	ghp->fcs = *(llc_hdr + len - 3);
+	ghp->fcs |= *(llc_hdr + len - 2) << 8;
+	ghp->fcs |= *(llc_hdr + len - 1) << 16;
+
+	/* Section 6.2.1: invalid PD field */
+	if (llc_hdr[0] & 0x80)
+		return -EIO;
+
+	/* This only works for the MS->SGSN direction */
+	if (llc_hdr[0] & 0x40)
+		ghp->is_cmd = 0;
+	else
+		ghp->is_cmd = 1;
+
+	ghp->sapi = llc_hdr[0] & 0xf;
+
+	/* Section 6.2.3: check for reserved SAPI */
+	switch (ghp->sapi) {
+	case 0:
+	case 4:
+	case 6:
+	case 0xa:
+	case 0xc:
+	case 0xd:
+	case 0xf:
+		return -EINVAL;
+	}
+
+	if ((ctrl[0] & 0x80) == 0) {
+		/* I (Information transfer + Supervisory) format */
+		uint8_t k;
+
+		ghp->data = ctrl + 3;
+
+		if (ctrl[0] & 0x40)
+			ghp->ack_req = 1;
+
+		ghp->seq_tx  = (ctrl[0] & 0x1f) << 4;
+		ghp->seq_tx |= (ctrl[1] >> 4);
+
+		ghp->seq_rx  = (ctrl[1] & 0x7) << 6;
+		ghp->seq_rx |= (ctrl[2] >> 2);
+
+		switch (ctrl[2] & 0x03) {
+		case 0:
+			ghp->cmd = GPRS_LLC_RR;
+			break;
+		case 1:
+			ghp->cmd = GPRS_LLC_ACK;
+			break;
+		case 2:
+			ghp->cmd = GPRS_LLC_RNR;
+			break;
+		case 3:
+			ghp->cmd = GPRS_LLC_SACK;
+			k = ctrl[3] & 0x1f;
+			ghp->data += 1 + k;
+			break;
+		}
+		ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+	} else if ((ctrl[0] & 0xc0) == 0x80) {
+		/* S (Supervisory) format */
+		ghp->data = NULL;
+		ghp->data_len = 0;
+
+		if (ctrl[0] & 0x20)
+			ghp->ack_req = 1;
+		ghp->seq_rx  = (ctrl[0] & 0x7) << 6;
+		ghp->seq_rx |= (ctrl[1] >> 2);
+
+		switch (ctrl[1] & 0x03) {
+		case 0:
+			ghp->cmd = GPRS_LLC_RR;
+			break;
+		case 1:
+			ghp->cmd = GPRS_LLC_ACK;
+			break;
+		case 2:
+			ghp->cmd = GPRS_LLC_RNR;
+			break;
+		case 3:
+			ghp->cmd = GPRS_LLC_SACK;
+			break;
+		}
+	} else if ((ctrl[0] & 0xe0) == 0xc0) {
+		/* UI (Unconfirmed Inforamtion) format */
+		ghp->cmd = GPRS_LLC_UI;
+		ghp->data = ctrl + 2;
+		ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+
+		ghp->seq_tx  = (ctrl[0] & 0x7) << 6;
+		ghp->seq_tx |= (ctrl[1] >> 2);
+		if (ctrl[1] & 0x02) {
+			ghp->is_encrypted = 1;
+			/* FIXME: encryption */
+		}
+		if (ctrl[1] & 0x01) {
+			/* FCS over hdr + all inf fields */
+		} else {
+			/* FCS over hdr + N202 octets (4) */
+			if (ghp->crc_length > UI_HDR_LEN + N202)
+				ghp->crc_length = UI_HDR_LEN + N202;
+		}
+	} else {
+		/* U (Unnumbered) format: 1 1 1 P/F M4 M3 M2 M1 */
+		ghp->data = NULL;
+		ghp->data_len = 0;
+
+		switch (ctrl[0] & 0xf) {
+		case GPRS_LLC_U_NULL_CMD:
+			ghp->cmd = GPRS_LLC_NULL;
+			break;
+		case GPRS_LLC_U_DM_RESP:
+			ghp->cmd = GPRS_LLC_DM;
+			break;
+		case GPRS_LLC_U_DISC_CMD:
+			ghp->cmd = GPRS_LLC_DISC;
+			break;
+		case GPRS_LLC_U_UA_RESP:
+			ghp->cmd = GPRS_LLC_UA;
+			break;
+		case GPRS_LLC_U_SABM_CMD:
+			ghp->cmd = GPRS_LLC_SABM;
+			break;
+		case GPRS_LLC_U_FRMR_RESP:
+			ghp->cmd = GPRS_LLC_FRMR;
+			break;
+		case GPRS_LLC_U_XID:
+			ghp->cmd = GPRS_LLC_XID;
+			ghp->data = ctrl + 1;
+			ghp->data_len = (llc_hdr + len - 3) - ghp->data;
+			break;
+		default:
+			return -EIO;
+		}
+	}
+
+	/* FIXME: parse sack frame */
+	if (ghp->cmd == GPRS_LLC_SACK) {
+		LOGP(DLLC, LOGL_NOTICE, "Unsupported SACK frame\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */
+int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv)
+{
+	struct bssgp_ud_hdr *udh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+	struct gprs_llc_hdr *lh = msgb_llch(msg);
+	struct gprs_llc_hdr_parsed llhp;
+	struct gprs_llc_lle *lle;
+	int rc = 0;
+
+	/* Identifiers from DOWN: NSEI, BVCI, TLLI */
+
+	memset(&llhp, 0, sizeof(llhp));
+	rc = gprs_llc_hdr_parse(&llhp, (uint8_t *) lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU));
+	gprs_llc_hdr_dump(&llhp);
+	if (rc < 0) {
+		LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n");
+		return rc;
+	}
+
+	switch (gprs_tlli_type(msgb_tlli(msg))) {
+	case TLLI_LOCAL:
+	case TLLI_FOREIGN:
+	case TLLI_RANDOM:
+	case TLLI_AUXILIARY:
+		break;
+	default:
+		LOGP(DLLC, LOGL_ERROR,
+			"Discarding frame with strange TLLI type\n");
+		break;
+	}
+
+	/* find the LLC Entity for this TLLI+SAPI tuple */
+	lle = lle_by_tlli_sapi(msgb_tlli(msg), llhp.sapi);
+
+	/* 7.2.1.1 LLC belonging to unassigned TLLI+SAPI shall be discarded,
+	 * except UID and XID frames with SAPI=1 */
+	if (!lle) {
+		if (llhp.sapi == GPRS_SAPI_GMM &&
+		    (llhp.cmd == GPRS_LLC_XID || llhp.cmd == GPRS_LLC_UI)) {
+			struct gprs_llc_llme *llme;
+			/* FIXME: don't use the TLLI but the 0xFFFF unassigned? */
+			llme = llme_alloc(msgb_tlli(msg));
+			LOGP(DLLC, LOGL_DEBUG, "LLC RX: unknown TLLI 0x08x, "
+				"creating LLME on the fly\n", msgb_tlli(msg));
+			lle = &llme->lle[llhp.sapi];
+		} else {
+			LOGP(DLLC, LOGL_NOTICE,
+				"unknown TLLI/SAPI: Silently dropping\n");
+			return 0;
+		}
+	}
+
+	/* decrypt information field + FCS, if needed! */
+	if (llhp.is_encrypted) {
+		uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */
+		uint16_t crypt_len = llhp.data_len + 3;
+		uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK];
+		uint32_t iv;
+		uint64_t kc = *(uint64_t *)&lle->llme->kc;
+		int rc, i;
+
+		if (lle->llme->algo == GPRS_ALGO_GEA0) {
+			LOGP(DLLC, LOGL_NOTICE, "encrypted frame for LLC that "
+				"has no KC/Algo! Dropping.\n");
+			return 0;
+		}
+
+		iv = gprs_cipher_gen_input_ui(iov_ui, lle->sapi, llhp.seq_tx,
+						lle->oc_ui_recv);
+		rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo,
+				     kc, iv, GPRS_CIPH_MS2SGSN);
+		if (rc < 0) {
+			LOGP(DLLC, LOGL_ERROR, "Error decrypting frame: %d\n",
+			     rc);
+			return rc;
+		}
+
+		/* XOR the cipher output with the information field + FCS */
+		for (i = 0; i < crypt_len; i++)
+			*(llhp.data + i) ^= cipher_out[i];
+	} else {
+		if (lle->llme->algo != GPRS_ALGO_GEA0) {
+			LOGP(DLLC, LOGL_NOTICE, "unencrypted frame for LLC "
+				"that is supposed to be encrypted. Dropping.\n");
+			return 0;
+		}
+	}
+
+	/* We have to do the FCS check _after_ decryption */
+	llhp.fcs_calc = gprs_llc_fcs((uint8_t *)lh, llhp.crc_length);
+	if (llhp.fcs != llhp.fcs_calc) {
+		LOGP(DLLC, LOGL_INFO, "Dropping frame with invalid FCS\n");
+		return -EIO;
+	}
+
+	/* Update LLE's (BVCI, NSEI) tuple */
+	lle->llme->bvci = msgb_bvci(msg);
+	lle->llme->nsei = msgb_nsei(msg);
+
+	/* Receive and Process the actual LLC frame */
+	rc = gprs_llc_hdr_rx(&llhp, lle);
+	if (rc < 0)
+		return rc;
+
+	/* llhp.data is only set when we need to send LL_[UNIT]DATA_IND up */
+	if (llhp.data) {
+		msgb_gmmh(msg) = llhp.data;
+		switch (llhp.sapi) {
+		case GPRS_SAPI_GMM:
+			/* send LL_UNITDATA_IND to GMM */
+			rc = gsm0408_gprs_rcvmsg(msg, lle->llme);
+			break;
+		case GPRS_SAPI_SNDCP3:
+		case GPRS_SAPI_SNDCP5:
+		case GPRS_SAPI_SNDCP9:
+		case GPRS_SAPI_SNDCP11:
+			/* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */
+			rc = sndcp_llunitdata_ind(msg, lle, llhp.data, llhp.data_len);
+			break;
+		case GPRS_SAPI_SMS:
+			/* FIXME */
+		case GPRS_SAPI_TOM2:
+		case GPRS_SAPI_TOM8:
+			/* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */
+		default:
+			LOGP(DLLC, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi);
+			rc = -EINVAL;
+			break;
+		}
+	}
+
+	return rc;
+}
+
+/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */
+int gprs_llgmm_assign(struct gprs_llc_llme *llme,
+		      uint32_t old_tlli, uint32_t new_tlli,
+		      enum gprs_ciph_algo alg, const uint8_t *kc)
+{
+	unsigned int i;
+
+	/* Update the crypto parameters */
+	llme->algo = alg;
+	if (alg != GPRS_ALGO_GEA0)
+		memcpy(llme->kc, kc, sizeof(llme->kc));
+
+	if (old_tlli == 0xffffffff && new_tlli != 0xffffffff) {
+		/* TLLI Assignment 8.3.1 */
+		/* New TLLI shall be assigned and used when (re)transmitting LLC frames */
+		/* If old TLLI != 0xffffffff was assigned to LLME, then TLLI
+		 * old is unassigned.  Only TLLI new shall be accepted when
+		 * received from peer. */
+		if (llme->old_tlli != 0xffffffff) {
+			llme->old_tlli = 0xffffffff;
+			llme->tlli = new_tlli;
+		} else {
+			/* If TLLI old == 0xffffffff was assigned to LLME, then this is
+			 * TLLI assignmemt according to 8.3.1 */
+			llme->old_tlli = 0xffffffff;
+			llme->tlli = new_tlli;
+			llme->state = GPRS_LLMS_ASSIGNED;
+			/* 8.5.3.1 For all LLE's */
+			for (i = 0; i < ARRAY_SIZE(llme->lle); i++) {
+				struct gprs_llc_lle *l = &llme->lle[i];
+				l->vu_send = l->vu_recv = 0;
+				l->retrans_ctr = 0;
+				l->state = GPRS_LLES_ASSIGNED_ADM;
+				/* FIXME Set parameters according to table 9 */
+			}
+		}
+	} else if (old_tlli != 0xffffffff && new_tlli != 0xffffffff) {
+		/* TLLI Change 8.3.2 */
+		/* Both TLLI Old and TLLI New are assigned; use New when
+		 * (re)transmitting.  Accept toth Old and New on Rx */
+		llme->old_tlli = llme->tlli;
+		llme->tlli = new_tlli;
+		llme->state = GPRS_LLMS_ASSIGNED;
+	} else if (old_tlli != 0xffffffff && new_tlli == 0xffffffff) {
+		/* TLLI Unassignment 8.3.3) */
+		llme->tlli = llme->old_tlli = 0;
+		llme->state = GPRS_LLMS_UNASSIGNED;
+		for (i = 0; i < ARRAY_SIZE(llme->lle); i++) {
+			struct gprs_llc_lle *l = &llme->lle[i];
+			l->state = GPRS_LLES_UNASSIGNED;
+		}
+		llme_free(llme);
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
+int gprs_llc_init(const char *cipher_plugin_path)
+{
+	return gprs_cipher_load(cipher_plugin_path);
+}
diff --git a/src/gprs/gprs_llc_vty.c b/src/gprs/gprs_llc_vty.c
new file mode 100644
index 0000000..d4f743b
--- /dev/null
+++ b/src/gprs/gprs_llc_vty.c
@@ -0,0 +1,108 @@
+/* VTY interface for our GPRS LLC implementation */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_llc.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+struct value_string gprs_llc_state_strs[] = {
+	{ GPRS_LLES_UNASSIGNED, 	"TLLI Unassigned" },
+	{ GPRS_LLES_ASSIGNED_ADM,	"TLLI Assigned" },
+	{ GPRS_LLES_LOCAL_EST,		"Local Establishment" },
+	{ GPRS_LLES_REMOTE_EST,		"Remote Establishment" },
+	{ GPRS_LLES_ABM,		"Asynchronous Balanced Mode" },
+	{ GPRS_LLES_LOCAL_REL,		"Local Release" },
+	{ GPRS_LLES_TIMER_REC,		"Timer Recovery" },
+};
+
+static void vty_dump_lle(struct vty *vty, struct gprs_llc_lle *lle)
+{
+	struct gprs_llc_params *par = &lle->params;
+	vty_out(vty, " SAPI %2u State %s VUsend=%u, VUrecv=%u", lle->sapi, 
+		get_value_string(gprs_llc_state_strs, lle->state),
+		lle->vu_send, lle->vu_recv);
+	vty_out(vty, " Vsent=%u Vack=%u Vrecv=%u, RetransCtr=%u%s",
+		lle->v_sent, lle->v_ack, lle->v_recv,
+		lle->retrans_ctr, VTY_NEWLINE);
+	vty_out(vty, "  T200=%u, N200=%u, N201-U=%u, N201-I=%u, mD=%u, "
+		"mU=%u, kD=%u, kU=%u%s", par->t200_201, par->n200,
+		par->n201_u, par->n201_i, par->mD, par->mU, par->kD,
+		par->kU, VTY_NEWLINE);
+}
+
+static uint8_t valid_sapis[] = { 1, 2, 3, 5, 7, 8, 9, 11 };
+
+static void vty_dump_llme(struct vty *vty, struct gprs_llc_llme *llme)
+{
+	unsigned int i;
+
+	vty_out(vty, "TLLI %08x (Old TLLI %08x) BVCI=%u NSEI=%u: State %s%s",
+		llme->tlli, llme->old_tlli, llme->bvci, llme->nsei,
+		get_value_string(gprs_llc_state_strs, llme->state), VTY_NEWLINE);
+
+	for (i = 0; i < ARRAY_SIZE(valid_sapis); i++) {
+		struct gprs_llc_lle *lle;
+		uint8_t sapi = valid_sapis[i];
+
+		if (sapi >= ARRAY_SIZE(llme->lle))
+			continue;
+
+		lle = &llme->lle[sapi];
+		vty_dump_lle(vty, lle);
+	}
+}
+
+
+DEFUN(show_llc, show_llc_cmd,
+	"show llc",
+	SHOW_STR "Display information about the LLC protocol")
+{
+	struct gprs_llc_llme *llme;
+
+	vty_out(vty, "State of LLC Entities%s", VTY_NEWLINE);
+	llist_for_each_entry(llme, &gprs_llc_llmes, list) {
+		vty_dump_llme(vty, llme);
+	}
+	return CMD_SUCCESS;
+}
+
+int gprs_llc_vty_init(void)
+{
+	install_element_ve(&show_llc_cmd);
+
+	return 0;
+}
diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c
new file mode 100644
index 0000000..4436554
--- /dev/null
+++ b/src/gprs/gprs_sgsn.c
@@ -0,0 +1,383 @@
+/* GPRS SGSN functionality */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_gmm.h>
+
+extern struct sgsn_instance *sgsn;
+
+LLIST_HEAD(sgsn_mm_ctxts);
+LLIST_HEAD(sgsn_ggsn_ctxts);
+LLIST_HEAD(sgsn_apn_ctxts);
+LLIST_HEAD(sgsn_pdp_ctxts);
+
+static const struct rate_ctr_desc mmctx_ctr_description[] = {
+	{ "sign.packets.in",	"Signalling Messages ( In)" },
+	{ "sign.packets.out",	"Signalling Messages (Out)" },
+	{ "udata.packets.in",	"User Data  Messages ( In)" },
+	{ "udata.packets.out",	"User Data  Messages (Out)" },
+	{ "udata.bytes.in",	"User Data  Bytes    ( In)" },
+	{ "udata.bytes.out",	"User Data  Bytes    (Out)" },
+	{ "pdp_ctx_act",	"PDP Context Activations  " },
+	{ "suspend",		"SUSPEND Count            " },
+	{ "paging.ps",		"Paging Packet Switched   " },
+	{ "paging.cs",		"Paging Circuit Switched  " },
+	{ "ra_update",		"Routing Area Update      " },
+};
+
+static const struct rate_ctr_group_desc mmctx_ctrg_desc = {
+	.group_name_prefix = "sgsn.mmctx",
+	.group_description = "SGSN MM Context Statistics",
+	.num_ctr = ARRAY_SIZE(mmctx_ctr_description),
+	.ctr_desc = mmctx_ctr_description,
+};
+
+static const struct rate_ctr_desc pdpctx_ctr_description[] = {
+	{ "udata.packets.in",	"User Data  Messages ( In)" },
+	{ "udata.packets.out",	"User Data  Messages (Out)" },
+	{ "udata.bytes.in",	"User Data  Bytes    ( In)" },
+	{ "udata.bytes.out",	"User Data  Bytes    (Out)" },
+};
+
+static const struct rate_ctr_group_desc pdpctx_ctrg_desc = {
+	.group_name_prefix = "sgsn.pdpctx",
+	.group_description = "SGSN PDP Context Statistics",
+	.num_ctr = ARRAY_SIZE(pdpctx_ctr_description),
+	.ctr_desc = pdpctx_ctr_description,
+};
+
+static int ra_id_equals(const struct gprs_ra_id *id1,
+			const struct gprs_ra_id *id2)
+{
+	return (id1->mcc == id2->mcc && id1->mnc == id2->mnc &&
+		id1->lac == id2->lac && id1->rac == id2->rac);
+}
+
+/* See 03.02 Chapter 2.6 */
+static inline uint32_t tlli_foreign(uint32_t tlli)
+{
+	return ((tlli | 0x80000000) & ~0x40000000);	
+}
+
+/* look-up a SGSN MM context based on TLLI + RAI */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
+					const struct gprs_ra_id *raid)
+{
+	struct sgsn_mm_ctx *ctx;
+	int tlli_type;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (tlli == ctx->tlli &&
+		    ra_id_equals(raid, &ctx->ra))
+			return ctx;
+	}
+
+	tlli_type = gprs_tlli_type(tlli);
+	switch (tlli_type) {
+	case TLLI_LOCAL:
+		llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+			if ((ctx->p_tmsi | 0xC0000000) == tlli ||
+			     (ctx->p_tmsi_old && (ctx->p_tmsi_old | 0xC0000000) == tlli)) {
+				ctx->tlli = tlli;
+				return ctx;
+			}
+		}
+		break;
+	case TLLI_FOREIGN:
+		llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+			if (tlli == tlli_foreign(ctx->tlli) &&
+			    ra_id_equals(raid, &ctx->ra))
+				return ctx;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi)
+{
+	struct sgsn_mm_ctx *ctx;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (p_tmsi == ctx->p_tmsi ||
+		    (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi))
+			return ctx;
+	}
+	return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi)
+{
+	struct sgsn_mm_ctx *ctx;
+
+	llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+		if (!strcmp(imsi, ctx->imsi))
+			return ctx;
+	}
+	return NULL;
+
+}
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+					const struct gprs_ra_id *raid)
+{
+	struct sgsn_mm_ctx *ctx;
+
+	ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
+	if (!ctx)
+		return NULL;
+
+	memcpy(&ctx->ra, raid, sizeof(ctx->ra));
+	ctx->tlli = tlli;
+	ctx->mm_state = GMM_DEREGISTERED;
+	ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, tlli);
+	INIT_LLIST_HEAD(&ctx->pdp_list);
+
+	llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+	return ctx;
+}
+
+void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm)
+{
+	struct sgsn_pdp_ctx *pdp, *pdp2;
+
+	/* Unlink from global list of MM contexts */
+	llist_del(&mm->list);
+
+	/* Free all PDP contexts */
+	llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list)
+		sgsn_pdp_ctx_free(pdp);
+	
+	rate_ctr_group_free(mm->ctrg);
+
+	talloc_free(mm);
+}
+
+/* look up PDP context by MM context and NSAPI */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+					   uint8_t nsapi)
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	llist_for_each_entry(pdp, &mm->pdp_list, list) {
+		if (pdp->nsapi == nsapi)
+			return pdp;
+	}
+	return NULL;
+}
+
+/* look up PDP context by MM context and transaction ID */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm,
+					 uint8_t tid)
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	llist_for_each_entry(pdp, &mm->pdp_list, list) {
+		if (pdp->ti == tid)
+			return pdp;
+	}
+	return NULL;
+}
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+					uint8_t nsapi)
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi);
+	if (pdp)
+		return NULL;
+
+	pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx);
+	if (!pdp)
+		return NULL;
+
+	pdp->mm = mm;
+	pdp->nsapi = nsapi;
+	pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi);
+	llist_add(&pdp->list, &mm->pdp_list);
+	llist_add(&pdp->g_list, &sgsn_pdp_ctxts);
+
+	return pdp;
+}
+
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp)
+{
+	rate_ctr_group_free(pdp->ctrg);
+	llist_del(&pdp->list);
+	llist_del(&pdp->g_list);
+	talloc_free(pdp);
+}
+
+/* GGSN contexts */
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id)
+{
+	struct sgsn_ggsn_ctx *ggc;
+
+	ggc = talloc_zero(tall_bsc_ctx, struct sgsn_ggsn_ctx);
+	if (!ggc)
+		return NULL;
+
+	ggc->id = id;
+	ggc->gtp_version = 1;
+	ggc->remote_restart_ctr = -1;
+	/* if we are called from config file parse, this gsn doesn't exist yet */
+	ggc->gsn = sgsn->gsn;
+	llist_add(&ggc->list, &sgsn_ggsn_ctxts);
+
+	return ggc;
+}
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id)
+{
+	struct sgsn_ggsn_ctx *ggc;
+
+	llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+		if (id == ggc->id)
+			return ggc;
+	}
+	return NULL;
+}
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr)
+{
+	struct sgsn_ggsn_ctx *ggc;
+
+	llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+		if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr)))
+			return ggc;
+	}
+	return NULL;
+}
+
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id)
+{
+	struct sgsn_ggsn_ctx *ggc;
+
+	ggc = sgsn_ggsn_ctx_by_id(id);
+	if (!ggc)
+		ggc = sgsn_ggsn_ctx_alloc(id);
+	return ggc;
+}
+
+/* APN contexts */
+
+#if 0
+struct apn_ctx *apn_ctx_alloc(const char *ap_name)
+{
+	struct apn_ctx *actx;
+
+	actx = talloc_zero(talloc_bsc_ctx, struct apn_ctx);
+	if (!actx)
+		return NULL;
+	actx->name = talloc_strdup(actx, ap_name);
+
+	return actx;
+}
+
+struct apn_ctx *apn_ctx_by_name(const char *name)
+{
+	struct apn_ctx *actx;
+
+	llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
+		if (!strcmp(name, actx->name))
+			return actx;
+	}
+	return NULL;
+}
+
+struct apn_ctx *apn_ctx_find_alloc(const char *name)
+{
+	struct apn_ctx *actx;
+
+	actx = apn_ctx_by_name(name);
+	if (!actx)
+		actx = apn_ctx_alloc(name);
+
+	return actx;
+}
+#endif
+
+uint32_t sgsn_alloc_ptmsi(void)
+{
+	struct sgsn_mm_ctx *mm;
+	uint32_t ptmsi;
+
+restart:
+	ptmsi = rand();
+	llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+		if (mm->p_tmsi == ptmsi)
+			goto restart;
+	}
+
+	return ptmsi;
+}
+
+static void drop_one_pdp(struct sgsn_pdp_ctx *pdp)
+{
+	if (pdp->mm->mm_state == GMM_REGISTERED_NORMAL)
+		gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
+	else  {
+		/* FIXME: GPRS paging in case MS is SUSPENDED */
+		LOGP(DGPRS, LOGL_NOTICE, "Hard-dropping PDP ctx due to GGSN "
+			"recovery\n");
+		sgsn_pdp_ctx_free(pdp);
+	}
+}
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * ottherwise lost state (recovery procedure) */
+int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn)
+{
+	struct sgsn_mm_ctx *mm;
+	int num = 0;
+
+	llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+		struct sgsn_pdp_ctx *pdp;
+		llist_for_each_entry(pdp, &mm->pdp_list, list) {
+			if (pdp->ggsn == ggsn) {
+				drop_one_pdp(pdp);
+				num++;
+			}
+		}
+	}
+
+	return num;
+}
diff --git a/src/gprs/gprs_sndcp.c b/src/gprs/gprs_sndcp.c
new file mode 100644
index 0000000..4f421e4
--- /dev/null
+++ b/src/gprs/gprs_sndcp.c
@@ -0,0 +1,616 @@
+/* GPRS SNDCP protocol implementation as per 3GPP TS 04.65 */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/linuxlist.h>
+#include <osmocore/timer.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/sgsn.h>
+
+#include "gprs_sndcp.h"
+
+/* Chapter 7.2: SN-PDU Formats */
+struct sndcp_common_hdr {
+	/* octet 1 */
+	uint8_t nsapi:4;
+	uint8_t more:1;
+	uint8_t type:1;
+	uint8_t first:1;
+	uint8_t spare:1;
+} __attribute__((packed));
+
+/* PCOMP / DCOMP only exist in first fragment */
+struct sndcp_comp_hdr {
+	/* octet 2 */
+	uint8_t pcomp:4;
+	uint8_t dcomp:4;
+} __attribute__((packed));
+
+struct sndcp_udata_hdr {
+	/* octet 3 */
+	uint8_t npdu_high:4;
+	uint8_t seg_nr:4;
+	/* octet 4 */
+	uint8_t npdu_low;
+} __attribute__((packed));
+
+
+static void *tall_sndcp_ctx;
+
+/* A fragment queue entry, containing one framgent of a N-PDU */
+struct defrag_queue_entry {
+	struct llist_head list;
+	/* segment number of this fragment */
+	uint32_t seg_nr;
+	/* length of the data area of this fragment */
+	uint32_t data_len;
+	/* pointer to the data of this fragment */
+	uint8_t *data;
+};
+
+LLIST_HEAD(gprs_sndcp_entities);
+
+/* Enqueue a fragment into the defragment queue */
+static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr,
+			  uint8_t *data, uint32_t data_len)
+{
+	struct defrag_queue_entry *dqe;
+
+	dqe = talloc_zero(tall_sndcp_ctx, struct defrag_queue_entry);
+	if (!dqe)
+		return -ENOMEM;
+	dqe->data = talloc_zero_size(dqe, data_len);
+	if (!dqe->data) {
+		talloc_free(dqe);
+		return -ENOMEM;
+	}
+	dqe->seg_nr = seg_nr;
+	dqe->data_len = data_len;
+
+	llist_add(&dqe->list, &sne->defrag.frag_list);
+
+	if (seg_nr > sne->defrag.highest_seg)
+		sne->defrag.highest_seg = seg_nr;
+
+	sne->defrag.seg_have |= (1 << seg_nr);
+	sne->defrag.tot_len += data_len;
+
+	memcpy(dqe->data, data, data_len);
+
+	return 0;
+}
+
+/* return if we have all segments of this N-PDU */
+static int defrag_have_all_segments(struct gprs_sndcp_entity *sne)
+{
+	uint32_t seg_needed = 0;
+	unsigned int i;
+
+	/* create a bitmask of needed segments */
+	for (i = 0; i <= sne->defrag.highest_seg; i++)
+		seg_needed |= (1 << i);
+
+	if (seg_needed == sne->defrag.seg_have)
+		return 1;
+
+	return 0;
+}
+
+static struct defrag_queue_entry *defrag_get_seg(struct gprs_sndcp_entity *sne,
+						 uint32_t seg_nr)
+{
+	struct defrag_queue_entry *dqe;
+
+	llist_for_each_entry(dqe, &sne->defrag.frag_list, list) {
+		if (dqe->seg_nr == seg_nr) {
+			llist_del(&dqe->list);
+			return dqe;
+		}
+	}
+	return NULL;
+}
+
+/* Perform actual defragmentation and create an output packet */
+static int defrag_segments(struct gprs_sndcp_entity *sne)
+{
+	struct msgb *msg;
+	unsigned int seg_nr;
+	uint8_t *npdu;
+
+	LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u "
+		"num_seg=%u tot_len=%u\n", sne->lle->llme->tlli, sne->nsapi,
+		sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len);
+	msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag");
+	if (!msg)
+		return -ENOMEM;
+
+	/* FIXME: message headers + identifiers */
+
+	npdu = msg->data;
+
+	for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) {
+		struct defrag_queue_entry *dqe;
+		uint8_t *data;
+
+		dqe = defrag_get_seg(sne, seg_nr);
+		if (!dqe) {
+			LOGP(DSNDCP, LOGL_ERROR, "Segment %u missing\n", seg_nr);
+			talloc_free(msg);
+			return -EIO;
+		}
+		/* actually append the segment to the N-PDU */
+		data = msgb_put(msg, dqe->data_len);
+		memcpy(data, dqe->data, dqe->data_len);
+
+		/* release memory for the fragment queue entry */
+		talloc_free(dqe);
+	}
+
+	/* FIXME: cancel timer */
+
+	/* actually send the N-PDU to the SGSN core code, which then
+	 * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
+	return sgsn_rx_sndcp_ud_ind(&sne->ra_id, sne->lle->llme->tlli,
+				    sne->nsapi, msg, sne->defrag.tot_len, npdu);
+}
+
+static int defrag_input(struct gprs_sndcp_entity *sne, struct msgb *msg, uint8_t *hdr,
+			unsigned int len)
+{
+	struct sndcp_common_hdr *sch;
+	struct sndcp_comp_hdr *scomph = NULL;
+	struct sndcp_udata_hdr *suh;
+	uint16_t npdu_num;
+	uint8_t *data;
+	int rc;
+
+	sch = (struct sndcp_common_hdr *) hdr;
+	if (sch->first) {
+		scomph = (struct sndcp_comp_hdr *) (hdr + 1);
+		suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
+	} else
+		suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));
+
+	data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr);
+
+	npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
+
+	LOGP(DSNDCP, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u "
+		"Length %u %s %s\n", sne->lle->llme->tlli, sne->nsapi, npdu_num,
+		suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : "");
+
+	if (sch->first) {
+		/* first segment of a new packet.  Discard all leftover fragments of
+		 * previous packet */
+		if (!llist_empty(&sne->defrag.frag_list)) {
+			struct defrag_queue_entry *dqe, *dqe2;
+			LOGP(DSNDCP, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping "
+			     "SN-PDU %u due to insufficient segments (%04x)\n",
+			     sne->lle->llme->tlli, sne->nsapi, sne->defrag.npdu,
+			     sne->defrag.seg_have);
+			llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) {
+				llist_del(&dqe->list);
+				talloc_free(dqe);
+			}
+		}
+		/* store the currently de-fragmented PDU number */
+		sne->defrag.npdu = npdu_num;
+
+		/* Re-set fragmentation state */
+		sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0;
+		sne->defrag.tot_len = 0;
+		/* FIXME: (re)start timer */
+	}
+
+	if (sne->defrag.npdu != npdu_num) {
+		LOGP(DSNDCP, LOGL_INFO, "Segment for different SN-PDU "
+			"(%u != %u)\n", npdu_num, sne->defrag.npdu);
+		/* FIXME */
+	}
+
+	/* FIXME: check if seg_nr already exists */
+	/* make sure to subtract length of SNDCP header from 'len' */
+	rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr));
+	if (rc < 0)
+		return rc;
+
+	if (!sch->more) {
+		/* this is suppsed to be the last segment of the N-PDU, but it
+		 * might well be not the last to arrive */
+		sne->defrag.no_more = 1;
+	}
+
+	if (sne->defrag.no_more) {
+		/* we have already received the last segment before, let's check
+		 * if all the previous segments exist */
+		if (defrag_have_all_segments(sne))
+			return defrag_segments(sne);
+	}
+
+	return 0;
+}
+
+static struct gprs_sndcp_entity *gprs_sndcp_entity_by_lle(const struct gprs_llc_lle *lle,
+						uint8_t nsapi)
+{
+	struct gprs_sndcp_entity *sne;
+
+	llist_for_each_entry(sne, &gprs_sndcp_entities, list) {
+		if (sne->lle == lle && sne->nsapi == nsapi)
+			return sne;
+	}
+	return NULL;
+}
+
+static struct gprs_sndcp_entity *gprs_sndcp_entity_alloc(struct gprs_llc_lle *lle,
+						uint8_t nsapi)
+{
+	struct gprs_sndcp_entity *sne;
+
+	sne = talloc_zero(tall_sndcp_ctx, struct gprs_sndcp_entity);
+	if (!sne)
+		return NULL;
+
+	sne->lle = lle;
+	sne->nsapi = nsapi;
+	sne->defrag.timer.data = sne;
+	//sne->fqueue.timer.cb = FIXME;
+	sne->rx_state = SNDCP_RX_S_FIRST;
+	INIT_LLIST_HEAD(&sne->defrag.frag_list);
+
+	llist_add(&sne->list, &gprs_sndcp_entities);
+
+	return sne;
+}
+
+/* Entry point for the SNSM-ACTIVATE.indication */
+int sndcp_sm_activate_ind(struct gprs_llc_lle *lle, uint8_t nsapi)
+{
+	LOGP(DSNDCP, LOGL_INFO, "SNSM-ACTIVATE.ind (lle=%p TLLI=%08x, "
+	     "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi);
+
+	if (gprs_sndcp_entity_by_lle(lle, nsapi)) {
+		LOGP(DSNDCP, LOGL_ERROR, "Trying to ACTIVATE "
+			"already-existing entity (TLLI=%08x, NSAPI=%u)\n",
+			lle->llme->tlli, nsapi);
+		return -EEXIST;
+	}
+
+	if (!gprs_sndcp_entity_alloc(lle, nsapi)) {
+		LOGP(DSNDCP, LOGL_ERROR, "Out of memory during ACTIVATE\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/* Entry point for the SNSM-DEACTIVATE.indication */
+int sndcp_sm_deactivate_ind(struct gprs_llc_lle *lle, uint8_t nsapi)
+{
+	struct gprs_sndcp_entity *sne;
+
+	LOGP(DSNDCP, LOGL_INFO, "SNSM-DEACTIVATE.ind (lle=%p, TLLI=%08x, "
+	     "SAPI=%u, NSAPI=%u)\n", lle, lle->llme->tlli, lle->sapi, nsapi);
+
+	sne = gprs_sndcp_entity_by_lle(lle, nsapi);
+	if (!sne) {
+		LOGP(DSNDCP, LOGL_ERROR, "SNSM-DEACTIVATE.ind for non-"
+		     "existing TLLI=%08x SAPI=%u NSAPI=%u\n", lle->llme->tlli,
+		     lle->sapi, nsapi);
+		return -ENOENT;
+	}
+	llist_del(&sne->list);
+	/* frag queue entries are hierarchically allocated, so no need to
+	 * free them explicitly here */
+	talloc_free(sne);
+
+	return 0;
+}
+
+/* Fragmenter state */
+struct sndcp_frag_state {
+	uint8_t frag_nr;
+	struct msgb *msg;	/* original message */
+	uint8_t *next_byte;	/* first byte of next fragment */
+
+	struct gprs_sndcp_entity *sne;
+	void *mmcontext;
+};
+
+/* returns '1' if there are more fragments to send, '0' if none */
+static int sndcp_send_ud_frag(struct sndcp_frag_state *fs)
+{
+	struct gprs_sndcp_entity *sne = fs->sne;
+	struct gprs_llc_lle *lle = sne->lle;
+	struct sndcp_common_hdr *sch;
+	struct sndcp_comp_hdr *scomph;
+	struct sndcp_udata_hdr *suh;
+	struct msgb *fmsg;
+	unsigned int max_payload_len;
+	unsigned int len;
+	uint8_t *data;
+	int rc, more;
+
+	fmsg = msgb_alloc_headroom(fs->sne->lle->params.n201_u+256, 128,
+				   "SNDCP Frag");
+	if (!fmsg)
+		return -ENOMEM;
+
+	/* make sure lower layers route the fragment like the original */
+	msgb_tlli(fmsg) = msgb_tlli(fs->msg);
+	msgb_bvci(fmsg) = msgb_bvci(fs->msg);
+	msgb_nsei(fmsg) = msgb_nsei(fs->msg);
+
+	/* prepend common SNDCP header */
+	sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch));
+	sch->nsapi = sne->nsapi;
+	/* Set FIRST bit if we are the first fragment in a series */
+	if (fs->frag_nr == 0)
+		sch->first = 1;
+	sch->type = 1;
+
+	/* append the compression header for first fragment */
+	if (sch->first) {
+		scomph = (struct sndcp_comp_hdr *)
+				msgb_put(fmsg, sizeof(*scomph));
+		scomph->pcomp = 0;
+		scomph->dcomp = 0;
+	}
+
+	/* append the user-data header */
+	suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh));
+	suh->npdu_low = sne->tx_npdu_nr & 0xff;
+	suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
+	suh->seg_nr = fs->frag_nr % 0xf;
+
+	/* calculate remaining length to be sent */
+	len = (fs->msg->data + fs->msg->len) - fs->next_byte;
+	/* how much payload can we actually send via LLC? */
+	max_payload_len = lle->params.n201_u - (sizeof(*sch) + sizeof(*suh));
+	if (sch->first)
+		max_payload_len -= sizeof(*scomph);
+	/* check if we're exceeding the max */
+	if (len > max_payload_len)
+		len = max_payload_len;
+
+	/* copy the actual fragment data into our fmsg */
+	data = msgb_put(fmsg, len);
+	memcpy(data, fs->next_byte, len);
+
+	/* Increment fragment number and data pointer to next fragment */
+	fs->frag_nr++;
+	fs->next_byte += len;
+
+	/* determine if we have more fragemnts to send */
+	if ((fs->msg->data + fs->msg->len) <= fs->next_byte)
+		more = 0;
+	else
+		more = 1;
+
+	/* set the MORE bit of the SNDCP header accordingly */
+	sch->more = more;
+
+	rc = gprs_llc_tx_ui(fmsg, lle->sapi, 0, fs->mmcontext);
+	if (rc < 0) {
+		/* abort in case of error, do not advance frag_nr / next_byte */
+		msgb_free(fmsg);
+		return rc;
+	}
+
+	if (!more) {
+		/* we've sent all fragments */
+		msgb_free(fs->msg);
+		memset(fs, 0, sizeof(*fs));
+		/* increment NPDU number for next frame */
+		sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
+		return 0;
+	}
+
+	/* default: more fragments to send */
+	return 1;
+}
+
+/* Request transmission of a SN-PDU over specified LLC Entity + SAPI */
+int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi,
+			void *mmcontext)
+{
+	struct gprs_sndcp_entity *sne;
+	struct sndcp_common_hdr *sch;
+	struct sndcp_comp_hdr *scomph;
+	struct sndcp_udata_hdr *suh;
+	struct sndcp_frag_state fs;
+
+	/* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */
+
+	sne = gprs_sndcp_entity_by_lle(lle, nsapi);
+	if (!sne) {
+		LOGP(DSNDCP, LOGL_ERROR, "Cannot find SNDCP Entity\n");
+		return -EIO;
+	}
+
+	/* Check if we need to fragment this N-PDU into multiple SN-PDUs */
+	if (msg->len > lle->params.n201_u - 
+			(sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) {
+		/* initialize the fragmenter state */
+		fs.msg = msg;
+		fs.frag_nr = 0;
+		fs.next_byte = msg->data;
+		fs.sne = sne;
+		fs.mmcontext = mmcontext;
+
+		/* call function to generate and send fragments until all
+		 * of the N-PDU has been sent */
+		while (1) {
+			int rc = sndcp_send_ud_frag(&fs);
+			if (rc == 0)
+				return 0;
+			if (rc < 0)
+				return rc;
+		}
+		/* not reached */
+		return 0;
+	}
+
+	/* this is the non-fragmenting case where we only build 1 SN-PDU */
+
+	/* prepend the user-data header */
+	suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh));
+	suh->npdu_low = sne->tx_npdu_nr & 0xff;
+	suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
+	suh->seg_nr = 0;
+	sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
+
+	scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph));
+	scomph->pcomp = 0;
+	scomph->dcomp = 0;
+
+	/* prepend common SNDCP header */
+	sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch));
+	sch->first = 1;
+	sch->type = 1;
+	sch->nsapi = nsapi;
+
+	return gprs_llc_tx_ui(msg, lle->sapi, 0, mmcontext);
+}
+
+/* Section 5.1.2.17 LL-UNITDATA.ind */
+int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle,
+			 uint8_t *hdr, uint16_t len)
+{
+	struct gprs_sndcp_entity *sne;
+	struct sndcp_common_hdr *sch = (struct sndcp_common_hdr *)hdr;
+	struct sndcp_comp_hdr *scomph = NULL;
+	struct sndcp_udata_hdr *suh;
+	uint8_t *npdu;
+	uint16_t npdu_num;
+	int npdu_len;
+
+	sch = (struct sndcp_common_hdr *) hdr;
+	if (sch->first) {
+		scomph = (struct sndcp_comp_hdr *) (hdr + 1);
+		suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
+	} else
+		suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));
+
+	if (sch->type == 0) {
+		LOGP(DSNDCP, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n");
+		return -EINVAL;
+	}
+
+	if (len < sizeof(*sch) + sizeof(*suh)) {
+		LOGP(DSNDCP, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len);
+		return -EIO;
+	}
+
+	sne = gprs_sndcp_entity_by_lle(lle, sch->nsapi);
+	if (!sne) {
+		LOGP(DSNDCP, LOGL_ERROR, "Message for non-existing SNDCP Entity "
+			"(lle=%p, TLLI=%08x, SAPI=%u, NSAPI=%u)\n", lle,
+			lle->llme->tlli, lle->sapi, sch->nsapi);
+		return -EIO;
+	}
+	/* FIXME: move this RA_ID up to the LLME or even higher */
+	bssgp_parse_cell_id(&sne->ra_id, msgb_bcid(msg));
+
+	/* any non-first segment is by definition something to defragment
+	 * as is any segment that tells us there are more segments */
+	if (!sch->first || sch->more)
+		return defrag_input(sne, msg, hdr, len);
+
+	if (scomph && (scomph->pcomp || scomph->dcomp)) {
+		LOGP(DSNDCP, LOGL_ERROR, "We don't support compression yet\n");
+		return -EIO;
+	}
+
+	npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
+	npdu = (uint8_t *)suh + sizeof(*suh);
+	npdu_len = (msg->data + msg->len) - npdu;
+	if (npdu_len <= 0) {
+		LOGP(DSNDCP, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len);
+		return -EIO;
+	}
+	/* actually send the N-PDU to the SGSN core code, which then
+	 * hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
+	return sgsn_rx_sndcp_ud_ind(&sne->ra_id, lle->llme->tlli, sne->nsapi, msg, npdu_len, npdu);
+}
+
+/* Section 5.1.2.1 LL-RESET.ind */
+static int sndcp_ll_reset_ind(struct gprs_sndcp_entity *se)
+{
+	/* treat all outstanding SNDCP-LLC request type primitives as not sent */
+	/* reset all SNDCP XID parameters to default values */
+}
+
+static int sndcp_ll_status_ind()
+{
+	/* inform the SM sub-layer by means of SNSM-STATUS.req */
+}
+
+#if 0
+static struct sndcp_state_list {{
+	uint32_t	states;
+	unsigned int	type;
+	int		(*rout)(struct gprs_sndcp_entity *se, struct msgb *msg);
+} sndcp_state_list[] = {
+	{ ALL_STATES,
+	  LL_RESET_IND, sndcp_ll_reset_ind },
+	{ ALL_STATES,
+	  LL_ESTABLISH_IND, sndcp_ll_est_ind },
+	{ SBIT(SNDCP_S_EST_RQD),
+	  LL_ESTABLISH_RESP, sndcp_ll_est_ind },
+	{ SBIT(SNDCP_S_EST_RQD),
+	  LL_ESTABLISH_CONF, sndcp_ll_est_conf },
+	{ SBIT(SNDCP_S_
+};
+
+static int sndcp_rx_llc_prim()
+{
+	case LL_ESTABLISH_REQ:
+	case LL_RELEASE_REQ:
+	case LL_XID_REQ:
+	case LL_DATA_REQ:
+	LL_UNITDATA_REQ,	/* TLLI, SN-PDU, Ref, QoS, Radio Prio, Ciph */
+
+	switch (prim) {
+	case LL_RESET_IND:
+	case LL_ESTABLISH_IND:
+	case LL_ESTABLISH_RESP:
+	case LL_ESTABLISH_CONF:
+	case LL_RELEASE_IND:
+	case LL_RELEASE_CONF:
+	case LL_XID_IND:
+	case LL_XID_RESP:
+	case LL_XID_CONF:
+	case LL_DATA_IND:
+	case LL_DATA_CONF:
+	case LL_UNITDATA_IND:
+	case LL_STATUS_IND:
+}
+#endif
diff --git a/src/gprs/gprs_sndcp.h b/src/gprs/gprs_sndcp.h
new file mode 100644
index 0000000..e9a50be
--- /dev/null
+++ b/src/gprs/gprs_sndcp.h
@@ -0,0 +1,53 @@
+#ifndef _INT_SNDCP_H
+#define _INT_SNDCP_H
+
+#include <stdint.h>
+#include <osmocore/linuxlist.h>
+
+/* A fragment queue header, maintaining list of fragments for one N-PDU */
+struct defrag_state {
+	/* PDU number for which the defragmentation state applies */
+	uint16_t npdu;
+	/* highest segment number we have received so far */
+	uint8_t highest_seg;
+	/* bitmask of the segments we already have */
+	uint32_t seg_have;
+	/* do we still expect more segments? */
+	unsigned int no_more;
+	/* total length of all segments together */
+	unsigned int tot_len;
+
+	/* linked list of defrag_queue_entry: one for each fragment  */
+	struct llist_head frag_list;
+
+	struct timer_list timer;
+};
+
+/* See 6.7.1.2 Reassembly */
+enum sndcp_rx_state {
+	SNDCP_RX_S_FIRST,
+	SNDCP_RX_S_SUBSEQ,
+	SNDCP_RX_S_DISCARD,
+};
+
+struct gprs_sndcp_entity {
+	struct llist_head list;
+
+	/* FIXME: move this RA_ID up to the LLME or even higher */
+	struct gprs_ra_id ra_id;
+	/* reference to the LLC Entity below this SNDCP entity */
+	struct gprs_llc_lle *lle;
+	/* The NSAPI we shall use on top of LLC */
+	uint8_t nsapi;
+
+	/* NPDU number for the GTP->SNDCP side */
+	uint16_t tx_npdu_nr;
+	/* SNDCP eeceiver state */
+	enum sndcp_rx_state rx_state;
+	/* The defragmentation queue */
+	struct defrag_state defrag;
+};
+
+extern struct llist_head gprs_sndcp_entities;
+
+#endif	/* INT_SNDCP_H */
diff --git a/src/gprs/gprs_sndcp_vty.c b/src/gprs/gprs_sndcp_vty.c
new file mode 100644
index 0000000..5a755d5
--- /dev/null
+++ b/src/gprs/gprs_sndcp_vty.c
@@ -0,0 +1,74 @@
+/* VTY interface for our GPRS SNDCP implementation */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_llc.h>
+
+#include "gprs_sndcp.h"
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+static void vty_dump_sne(struct vty *vty, struct gprs_sndcp_entity *sne)
+{
+	unsigned int i;
+
+	vty_out(vty, " TLLI %08x SAPI=%u NSAPI=%u:%s",
+		sne->lle->llme->tlli, sne->lle->sapi, sne->nsapi, VTY_NEWLINE);
+	vty_out(vty, "  Defrag: npdu=%u highest_seg=%u seg_have=0x%08x tot_len=%u%s",
+		sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.seg_have,
+		sne->defrag.tot_len, VTY_NEWLINE);
+}
+
+
+DEFUN(show_sndcp, show_sndcp_cmd,
+	"show sndcp",
+	SHOW_STR "Display information about the SNDCP protocol")
+{
+	struct gprs_sndcp_entity *sne;
+
+	vty_out(vty, "State of SNDCP Entities%s", VTY_NEWLINE);
+	llist_for_each_entry(sne, &gprs_sndcp_entities, list)
+		vty_dump_sne(vty, sne);
+
+	return CMD_SUCCESS;
+}
+
+int gprs_sndcp_vty_init(void)
+{
+	install_element_ve(&show_sndcp_cmd);
+
+	return 0;
+}
diff --git a/src/gprs/sgsn_libgtp.c b/src/gprs/sgsn_libgtp.c
new file mode 100644
index 0000000..7b10a45
--- /dev/null
+++ b/src/gprs/sgsn_libgtp.c
@@ -0,0 +1,610 @@
+/* GPRS SGSN integration with libgtp of OpenGGSN */
+/* libgtp implements the GPRS Tunelling Protocol GTP per TS 09.60 / 29.060 */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/gsm_04_08_gprs.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+
+#include <gtp.h>
+#include <pdp.h>
+
+const struct value_string gtp_cause_strs[] = {
+	{ GTPCAUSE_REQ_IMSI, "Request IMSI" },
+	{ GTPCAUSE_REQ_IMEI, "Request IMEI" },
+	{ GTPCAUSE_REQ_IMSI_IMEI, "Request IMSI and IMEI" },
+	{ GTPCAUSE_NO_ID_NEEDED, "No identity needed" },
+	{ GTPCAUSE_MS_REFUSES_X, "MS refuses" },
+	{ GTPCAUSE_MS_NOT_RESP_X, "MS is not GPRS responding" },
+	{ GTPCAUSE_ACC_REQ, "Request accepted" },
+	{ GTPCAUSE_NON_EXIST, "Non-existent" },
+	{ GTPCAUSE_INVALID_MESSAGE, "Invalid message format" },
+	{ GTPCAUSE_IMSI_NOT_KNOWN, "IMSI not known" },
+	{ GTPCAUSE_MS_DETACHED, "MS is GPRS detached" },
+	{ GTPCAUSE_MS_NOT_RESP, "MS is not GPRS responding" },
+	{ GTPCAUSE_MS_REFUSES, "MS refuses" },
+	{ GTPCAUSE_NO_RESOURCES, "No resources available" },
+	{ GTPCAUSE_NOT_SUPPORTED, "Service not supported" },
+	{ GTPCAUSE_MAN_IE_INCORRECT, "Mandatory IE incorrect" },
+	{ GTPCAUSE_MAN_IE_MISSING, "Mandatory IE missing" },
+	{ GTPCAUSE_OPT_IE_INCORRECT, "Optional IE incorrect" },
+	{ GTPCAUSE_SYS_FAIL, "System failure" },
+	{ GTPCAUSE_ROAMING_REST, "Roaming restrictions" },
+	{ GTPCAUSE_PTIMSI_MISMATCH, "P-TMSI Signature mismatch" },
+	{ GTPCAUSE_CONN_SUSP, "GPRS connection suspended" },
+	{ GTPCAUSE_AUTH_FAIL, "Authentication failure" },
+	{ GTPCAUSE_USER_AUTH_FAIL, "User authentication failed" },
+	{ GTPCAUSE_CONTEXT_NOT_FOUND, "Context not found" },
+	{ GTPCAUSE_ADDR_OCCUPIED, "All dynamic PDP addresses occupied" },
+	{ GTPCAUSE_NO_MEMORY, "No memory is available" },
+	{ GTPCAUSE_RELOC_FAIL, "Relocation failure" },
+	{ GTPCAUSE_UNKNOWN_MAN_EXTHEADER, "Unknown mandatory ext. header" },
+	{ GTPCAUSE_SEM_ERR_TFT, "Semantic error in TFT operation" },
+	{ GTPCAUSE_SYN_ERR_TFT, "Syntactic error in TFT operation" },
+	{ GTPCAUSE_SEM_ERR_FILTER, "Semantic errors in packet filter" },
+	{ GTPCAUSE_SYN_ERR_FILTER, "Syntactic errors in packet filter" },
+	{ GTPCAUSE_MISSING_APN, "Missing or unknown APN" },
+	{ GTPCAUSE_UNKNOWN_PDP, "Unknown PDP address or PDP type" },
+	{ 0, NULL }
+};
+
+/* Generate the GTP IMSI IE according to 09.60 Section 7.9.2 */
+static uint64_t imsi_str2gtp(char *str)
+{
+	uint64_t imsi64 = 0;
+	unsigned int n;
+	unsigned int imsi_len = strlen(str);
+
+	if (imsi_len > 16) {
+		LOGP(DGPRS, LOGL_NOTICE, "IMSI length > 16 not supported!\n");
+		return 0;
+	}
+
+	for (n = 0; n < 16; n++) {
+		uint64_t val;
+		if (n < imsi_len)
+			val = (str[n]-'0') & 0xf;
+		else
+			val = 0xf;
+		imsi64 |= (val << (n*4));
+	}
+	return imsi64;
+}
+
+/* generate a PDP context based on the IE's from the 04.08 message,
+ * and send the GTP create pdp context request to the GGSN */
+struct sgsn_pdp_ctx *sgsn_create_pdp_ctx(struct sgsn_ggsn_ctx *ggsn,
+					 struct sgsn_mm_ctx *mmctx,
+					 uint16_t nsapi,
+					 struct tlv_parsed *tp)
+{
+	struct sgsn_pdp_ctx *pctx;
+	struct pdp_t *pdp;
+	uint64_t imsi_ui64;
+	int rc;
+
+	LOGP(DGPRS, LOGL_ERROR, "Create PDP Context\n");
+	pctx = sgsn_pdp_ctx_alloc(mmctx, nsapi);
+	if (!pctx) {
+		LOGP(DGPRS, LOGL_ERROR, "Couldn't allocate PDP Ctx\n");
+		return NULL;
+	}
+
+	imsi_ui64 = imsi_str2gtp(mmctx->imsi);
+
+	rc = pdp_newpdp(&pdp, imsi_ui64, nsapi, NULL);
+	if (rc) {
+		LOGP(DGPRS, LOGL_ERROR, "Out of libgtp PDP Contexts\n");
+		return NULL;
+	}
+	pdp->priv = pctx;
+	pctx->lib = pdp;
+	pctx->ggsn = ggsn;
+
+	//pdp->peer =	/* sockaddr_in of GGSN (receive) */
+	//pdp->ipif =	/* not used by library */
+	pdp->version = ggsn->gtp_version;
+	pdp->hisaddr0 =	ggsn->remote_addr;
+	pdp->hisaddr1 = ggsn->remote_addr;
+	//pdp->cch_pdp = 512;	/* Charging Flat Rate */
+
+	/* MS provided APN, subscription not verified */
+	pdp->selmode = 0x01;
+
+	/* IMSI, TEID/TEIC, FLLU/FLLC, TID, NSAPI set in pdp_newpdp */
+
+	/* FIXME: MSISDN in BCD format from mmctx */
+	//pdp->msisdn.l/.v
+
+	/* End User Address from GMM requested PDP address */
+	pdp->eua.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_PDP_ADDR);
+	if (pdp->eua.l > sizeof(pdp->eua.v))
+		pdp->eua.l = sizeof(pdp->eua.v);
+	memcpy(pdp->eua.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_PDP_ADDR),
+		pdp->eua.l);
+	/* Highest 4 bits of first byte need to be set to 1, otherwise
+	 * the IE is identical with the 04.08 PDP Address IE */
+	pdp->eua.v[0] |= 0xf0;
+
+	/* APN name from GMM */
+	pdp->apn_use.l = TLVP_LEN(tp, GSM48_IE_GSM_APN);
+	if (pdp->apn_use.l > sizeof(pdp->apn_use.v))
+		pdp->apn_use.l = sizeof(pdp->apn_use.v);
+	memcpy(pdp->apn_use.v, TLVP_VAL(tp, GSM48_IE_GSM_APN),
+		pdp->apn_use.l);
+
+	/* Protocol Configuration Options from GMM */
+	pdp->pco_req.l = TLVP_LEN(tp, GSM48_IE_GSM_PROTO_CONF_OPT);
+	if (pdp->pco_req.l > sizeof(pdp->pco_req.v))
+		pdp->pco_req.l = sizeof(pdp->pco_req.v);
+	memcpy(pdp->pco_req.v, TLVP_VAL(tp, GSM48_IE_GSM_PROTO_CONF_OPT),
+		pdp->pco_req.l);
+
+	/* QoS options from GMM */
+	pdp->qos_req.l = TLVP_LEN(tp, OSMO_IE_GSM_REQ_QOS);
+	if (pdp->qos_req.l > sizeof(pdp->qos_req.v))
+		pdp->qos_req.l = sizeof(pdp->qos_req.v);
+	memcpy(pdp->qos_req.v, TLVP_VAL(tp, OSMO_IE_GSM_REQ_QOS),
+		pdp->qos_req.l);
+
+	/* SGSN address for control plane */
+	pdp->gsnlc.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr);
+	memcpy(pdp->gsnlc.v, &sgsn->cfg.gtp_listenaddr.sin_addr,
+		sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+
+	/* SGSN address for user plane */
+	pdp->gsnlu.l = sizeof(sgsn->cfg.gtp_listenaddr.sin_addr);
+	memcpy(pdp->gsnlu.v, &sgsn->cfg.gtp_listenaddr.sin_addr,
+		sizeof(sgsn->cfg.gtp_listenaddr.sin_addr));
+
+	/* change pdp state to 'requested' */
+	pctx->state = PDP_STATE_CR_REQ;
+
+	rc = gtp_create_context_req(ggsn->gsn, pdp, pctx);
+	/* FIXME */
+
+	return pctx;
+}
+
+/* SGSN wants to delete a PDP context */
+int sgsn_delete_pdp_ctx(struct sgsn_pdp_ctx *pctx)
+{
+	LOGP(DGPRS, LOGL_ERROR, "Delete PDP Context\n");
+
+	/* FIXME: decide if we need teardown or not ! */
+	return gtp_delete_context_req(pctx->ggsn->gsn, pctx->lib, pctx, 1);
+}
+
+struct cause_map {
+	uint8_t cause_in;
+	uint8_t cause_out;
+};
+
+static uint8_t cause_map(const struct cause_map *map, uint8_t in, uint8_t deflt)
+{
+	const struct cause_map *m;
+
+	for (m = map; m->cause_in && m->cause_out; m++) {
+		if (m->cause_in == in)
+			return m->cause_out;
+	}
+	return deflt;
+}
+
+/* how do we map from gtp cause to SM cause */
+static const struct cause_map gtp2sm_cause_map[] = {
+	{ GTPCAUSE_NO_RESOURCES, 	GSM_CAUSE_INSUFF_RSRC },
+	{ GTPCAUSE_NOT_SUPPORTED,	GSM_CAUSE_SERV_OPT_NOTSUPP },
+	{ GTPCAUSE_MAN_IE_INCORRECT,	GSM_CAUSE_INV_MAND_INFO },
+	{ GTPCAUSE_MAN_IE_MISSING,	GSM_CAUSE_INV_MAND_INFO },
+	{ GTPCAUSE_OPT_IE_INCORRECT,	GSM_CAUSE_PROTO_ERR_UNSPEC },
+	{ GTPCAUSE_SYS_FAIL,		GSM_CAUSE_NET_FAIL },
+	{ GTPCAUSE_ROAMING_REST,	GSM_CAUSE_REQ_SERV_OPT_NOTSUB },
+	{ GTPCAUSE_PTIMSI_MISMATCH,	GSM_CAUSE_PROTO_ERR_UNSPEC },
+	{ GTPCAUSE_CONN_SUSP,		GSM_CAUSE_PROTO_ERR_UNSPEC },
+	{ GTPCAUSE_AUTH_FAIL,		GSM_CAUSE_AUTH_FAILED },
+	{ GTPCAUSE_USER_AUTH_FAIL,	GSM_CAUSE_ACT_REJ_GGSN },
+	{ GTPCAUSE_CONTEXT_NOT_FOUND,	GSM_CAUSE_PROTO_ERR_UNSPEC },
+	{ GTPCAUSE_ADDR_OCCUPIED,	GSM_CAUSE_INSUFF_RSRC },
+	{ GTPCAUSE_NO_MEMORY,		GSM_CAUSE_INSUFF_RSRC },
+	{ GTPCAUSE_RELOC_FAIL,		GSM_CAUSE_PROTO_ERR_UNSPEC },
+	{ GTPCAUSE_UNKNOWN_MAN_EXTHEADER, GSM_CAUSE_PROTO_ERR_UNSPEC },
+	{ GTPCAUSE_MISSING_APN,		GSM_CAUSE_MISSING_APN },
+	{ GTPCAUSE_UNKNOWN_PDP,		GSM_CAUSE_UNKNOWN_PDP },
+	{ 0, 0 }
+};
+
+/* The GGSN has confirmed the creation of a PDP Context */
+static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
+{
+	struct sgsn_pdp_ctx *pctx = cbp;
+	uint8_t reject_cause;
+	int rc;
+
+	DEBUGP(DGPRS, "Received CREATE PDP CTX CONF, cause=%d(%s)\n",
+		cause, get_value_string(gtp_cause_strs, cause));
+
+	/* Check for cause value if it was really successful */
+	if (cause < 0) {
+		LOGP(DGPRS, LOGL_NOTICE, "Create PDP ctx req timed out\n");
+		if (pdp && pdp->version == 1) {
+			pdp->version = 0;
+			gtp_create_context_req(sgsn->gsn, pdp, cbp);
+			return 0;
+		} else {
+			reject_cause = GSM_CAUSE_NET_FAIL;
+			goto reject;
+		}
+	}
+
+	/* Check for cause value if it was really successful */
+	if (cause != GTPCAUSE_ACC_REQ) {
+		reject_cause = cause_map(gtp2sm_cause_map, cause,
+					 GSM_CAUSE_ACT_REJ_GGSN);
+		goto reject;
+	}
+
+	/* Activate the SNDCP layer */
+	sndcp_sm_activate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi);
+
+	/* Send PDP CTX ACT to MS */
+	return gsm48_tx_gsm_act_pdp_acc(pctx);
+
+reject:
+	pctx->state = PDP_STATE_NONE;
+	if (pdp)
+		pdp_freepdp(pdp);
+	/* Send PDP CTX ACT REJ to MS */
+	rc = gsm48_tx_gsm_act_pdp_rej(pctx->mm, pctx->ti, reject_cause,
+					0, NULL);
+	sgsn_pdp_ctx_free(pctx);
+
+	return EOF;
+}
+
+/* Confirmation of a PDP Context Delete */
+static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
+{
+	struct sgsn_pdp_ctx *pctx = cbp;
+	int rc;
+
+	DEBUGP(DGPRS, "Received DELETE PDP CTX CONF, cause=%d(%s)\n",
+		cause, get_value_string(gtp_cause_strs, cause));
+
+	/* Deactivate the SNDCP layer */
+	sndcp_sm_deactivate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi);
+
+	/* Confirm deactivation of PDP context to MS */
+	rc = gsm48_tx_gsm_deact_pdp_acc(pctx);
+
+	sgsn_pdp_ctx_free(pctx);
+
+	return rc;
+}
+
+/* Confirmation of an GTP ECHO request */
+static int echo_conf(struct pdp_t *pdp, void *cbp, int recovery)
+{
+	if (recovery < 0) {
+		DEBUGP(DGPRS, "GTP Echo Request timed out\n");
+		/* FIXME: if version == 1, retry with version 0 */
+	} else {
+		DEBUGP(DGPRS, "GTP Rx Echo Response\n");
+	}
+	return 0;
+}
+
+/* Any message received by GGSN contains a recovery IE */
+static int cb_recovery(struct sockaddr_in *peer, uint8_t recovery)
+{
+	struct sgsn_ggsn_ctx *ggsn;
+	
+	ggsn = sgsn_ggsn_ctx_by_addr(&peer->sin_addr);
+	if (!ggsn) {
+		DEBUGP(DGPRS, "Received Recovery IE for unknown GGSN\n");
+		return -EINVAL;
+	}
+
+	if (ggsn->remote_restart_ctr == -1) {
+		/* First received ECHO RESPONSE, note the restart ctr */
+		ggsn->remote_restart_ctr = recovery;
+	} else if (ggsn->remote_restart_ctr != recovery) {
+		/* counter has changed (GGSN restart): release all PDP */
+		LOGP(DGPRS, LOGL_NOTICE, "GGSN recovery (%u->%u), "
+		     "releasing all PDP contexts\n",
+		     ggsn->remote_restart_ctr, recovery);
+		ggsn->remote_restart_ctr = recovery;
+		drop_all_pdp_for_ggsn(ggsn);
+	}
+	return 0;
+}
+
+/* libgtp callback for confirmations */
+static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp)
+{
+	DEBUGP(DGPRS, "libgtp cb_conf(type=%d, cause=%d, pdp=%p, cbp=%p)\n",
+		type, cause, pdp, cbp);
+
+	if (cause == EOF)
+		LOGP(DGPRS, LOGL_ERROR, "libgtp EOF (type=%u, pdp=%p, cbp=%p)\n",
+			type, pdp, cbp);
+
+	switch (type) {
+	case GTP_ECHO_REQ:
+		/* libgtp hands us the RECOVERY number instead of a cause */
+		return echo_conf(pdp, cbp, cause);
+	case GTP_CREATE_PDP_REQ:
+		return create_pdp_conf(pdp, cbp, cause);
+	case GTP_DELETE_PDP_REQ:
+		return delete_pdp_conf(pdp, cbp, cause);
+	default:
+		break;
+	}
+	return 0;
+}
+
+/* Called whenever a PDP context is deleted for any reason */
+static int cb_delete_context(struct pdp_t *pdp)
+{
+	LOGP(DGPRS, LOGL_INFO, "PDP Context was deleted\n");
+	return 0;
+}
+
+/* Called when we receive a Version Not Supported message */
+static int cb_unsup_ind(struct sockaddr_in *peer)
+{
+	LOGP(DGPRS, LOGL_INFO, "GTP Version not supported Indication "
+		"from %s:%u\n", inet_ntoa(peer->sin_addr),
+		ntohs(peer->sin_port));
+	return 0;
+}
+
+/* Called when we receive a Supported Ext Headers Notification */
+static int cb_extheader_ind(struct sockaddr_in *peer)
+{
+	LOGP(DGPRS, LOGL_INFO, "GTP Supported Ext Headers Noficiation "
+		"from %s:%u\n", inet_ntoa(peer->sin_addr),
+		ntohs(peer->sin_port));
+	return 0;
+}
+
+/* Called whenever we recive a DATA packet */
+static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len)
+{
+	struct bssgp_paging_info pinfo;
+	struct sgsn_pdp_ctx *pdp;
+	struct sgsn_mm_ctx *mm;
+	struct msgb *msg;
+	uint8_t *ud;
+	int rc;
+
+	DEBUGP(DGPRS, "GTP DATA IND from GGSN, length=%u\n", len);
+
+	pdp = lib->priv;
+	if (!pdp) {
+		DEBUGP(DGPRS, "GTP DATA IND from GGSN for unknown PDP\n");
+		return -EIO;
+	}
+	mm = pdp->mm;
+
+	msg = msgb_alloc_headroom(len+256, 128, "GTP->SNDCP");
+	ud = msgb_put(msg, len);
+	memcpy(ud, packet, len);
+
+	msgb_tlli(msg) = mm->tlli;
+	msgb_bvci(msg) = mm->bvci;
+	msgb_nsei(msg) = mm->nsei;
+
+	switch (mm->mm_state) {
+	case GMM_REGISTERED_SUSPENDED:
+		/* initiate PS PAGING procedure */
+		memset(&pinfo, 0, sizeof(pinfo));
+		pinfo.mode = BSSGP_PAGING_PS;
+		pinfo.scope = BSSGP_PAGING_BVCI;
+		pinfo.bvci = mm->bvci;
+		pinfo.imsi = mm->imsi;
+		pinfo.ptmsi = &mm->p_tmsi;
+		pinfo.drx_params = mm->drx_parms;
+		pinfo.qos[0] = 0; // FIXME
+		rc = gprs_bssgp_tx_paging(mm->nsei, 0, &pinfo);
+		rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PAGING_PS]);
+		/* FIXME: queue the packet we received from GTP */
+		break;
+	case GMM_REGISTERED_NORMAL:
+		break;
+	default:
+		LOGP(DGPRS, LOGL_ERROR, "GTP DATA IND for TLLI %08X in state "
+			"%u\n", mm->tlli, mm->mm_state);
+		msgb_free(msg);
+		return -1;
+	}
+
+	rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_OUT]);
+	rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_OUT], len);
+	rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_UDATA_OUT]);
+	rate_ctr_add(&mm->ctrg->ctr[GMM_CTR_BYTES_UDATA_OUT], len);
+
+	return sndcp_unitdata_req(msg, &mm->llme->lle[pdp->sapi],
+				  pdp->nsapi, mm);
+}
+
+/* Called by SNDCP when it has received/re-assembled a N-PDU */
+int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi,
+			 struct msgb *msg, uint32_t npdu_len, uint8_t *npdu)
+{
+	struct sgsn_mm_ctx *mmctx;
+	struct sgsn_pdp_ctx *pdp;
+
+	/* look-up the MM context for this message */
+	mmctx = sgsn_mm_ctx_by_tlli(tlli, ra_id);
+	if (!mmctx) {
+		LOGP(DGPRS, LOGL_ERROR,
+			"Cannot find MM CTX for TLLI %08x\n", tlli);
+		return -EIO;
+	}
+	/* look-up the PDP context for this message */
+	pdp = sgsn_pdp_ctx_by_nsapi(mmctx, nsapi);
+	if (!pdp) {
+		LOGP(DGPRS, LOGL_ERROR, "Cannot find PDP CTX for "
+			"TLLI=%08x, NSAPI=%u\n", tlli, nsapi);
+		return -EIO;
+	}
+	if (!pdp->lib) {
+		LOGP(DGPRS, LOGL_ERROR, "PDP CTX without libgtp\n");
+		return -EIO;
+	}
+
+	rate_ctr_inc(&pdp->ctrg->ctr[PDP_CTR_PKTS_UDATA_IN]);
+	rate_ctr_add(&pdp->ctrg->ctr[PDP_CTR_BYTES_UDATA_IN], npdu_len);
+	rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_UDATA_IN]);
+	rate_ctr_add(&mmctx->ctrg->ctr[GMM_CTR_BYTES_UDATA_IN], npdu_len);
+
+	return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len);
+
+	return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len);
+}
+
+/* libgtp select loop integration */
+static int sgsn_gtp_fd_cb(struct bsc_fd *fd, unsigned int what)
+{
+	struct sgsn_instance *sgi = fd->data;
+	int rc;
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	switch (fd->priv_nr) {
+	case 0:
+		rc = gtp_decaps0(sgi->gsn);
+		break;
+	case 1:
+		rc = gtp_decaps1c(sgi->gsn);
+		break;
+	case 2:
+		rc = gtp_decaps1u(sgi->gsn);
+		break;
+	default:
+		rc = -EINVAL;
+		break;
+	}
+	return rc;
+}
+
+static void sgsn_gtp_tmr_start(struct sgsn_instance *sgi)
+{
+	struct timeval next;
+
+	/* Retrieve next retransmission as struct timeval */
+	gtp_retranstimeout(sgi->gsn, &next);
+
+	/* re-schedule the timer */
+	bsc_schedule_timer(&sgi->gtp_timer, next.tv_sec, next.tv_usec/1000);
+}
+
+/* timer callback for libgtp retransmissions and ping */
+static void sgsn_gtp_tmr_cb(void *data)
+{
+	struct sgsn_instance *sgi = data;
+
+	/* Do all the retransmissions as needed */
+	gtp_retrans(sgi->gsn);
+
+	sgsn_gtp_tmr_start(sgi);
+}
+
+int sgsn_gtp_init(struct sgsn_instance *sgi)
+{
+	int rc;
+	struct gsn_t *gsn;
+
+	rc = gtp_new(&sgi->gsn, sgi->cfg.gtp_statedir,
+		     &sgi->cfg.gtp_listenaddr.sin_addr, GTP_MODE_SGSN);
+	if (rc) {
+		LOGP(DGPRS, LOGL_ERROR, "Failed to create GTP: %d\n", rc);
+		return rc;
+	}
+	gsn = sgi->gsn;
+
+	sgi->gtp_fd0.fd = gsn->fd0;
+	sgi->gtp_fd0.priv_nr = 0;
+	sgi->gtp_fd0.data = sgi;
+	sgi->gtp_fd0.when = BSC_FD_READ;
+	sgi->gtp_fd0.cb = sgsn_gtp_fd_cb;
+	rc = bsc_register_fd(&sgi->gtp_fd0);
+	if (rc < 0)
+		return rc;
+
+	sgi->gtp_fd1c.fd = gsn->fd1c;
+	sgi->gtp_fd1c.priv_nr = 1;
+	sgi->gtp_fd1c.data = sgi;
+	sgi->gtp_fd1c.when = BSC_FD_READ;
+	sgi->gtp_fd1c.cb = sgsn_gtp_fd_cb;
+	bsc_register_fd(&sgi->gtp_fd1c);
+	if (rc < 0)
+		return rc;
+
+	sgi->gtp_fd1u.fd = gsn->fd1u;
+	sgi->gtp_fd1u.priv_nr = 2;
+	sgi->gtp_fd1u.data = sgi;
+	sgi->gtp_fd1u.when = BSC_FD_READ;
+	sgi->gtp_fd1u.cb = sgsn_gtp_fd_cb;
+	bsc_register_fd(&sgi->gtp_fd1u);
+	if (rc < 0)
+		return rc;
+
+	/* Start GTP re-transmission timer */
+	sgi->gtp_timer.cb = sgsn_gtp_tmr_cb;
+	sgi->gtp_timer.data = sgi;
+	sgsn_gtp_tmr_start(sgi);
+
+	/* Register callbackcs with libgtp */
+	gtp_set_cb_delete_context(gsn, cb_delete_context);
+	gtp_set_cb_conf(gsn, cb_conf);
+	gtp_set_cb_recovery(gsn, cb_recovery);
+	gtp_set_cb_data_ind(gsn, cb_data_ind);
+	gtp_set_cb_unsup_ind(gsn, cb_unsup_ind);
+	gtp_set_cb_extheader_ind(gsn, cb_extheader_ind);
+
+	return 0;
+}
diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c
new file mode 100644
index 0000000..c59265f
--- /dev/null
+++ b/src/gprs/sgsn_main.c
@@ -0,0 +1,288 @@
+/* GPRS SGSN Implementation */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <osmocore/logging.h>
+#include <osmocore/process.h>
+
+#include <osmocom/vty/telnet_interface.h>
+
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+
+#include <gtp.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+void *tall_bsc_ctx;
+
+struct gprs_ns_inst *sgsn_nsi;
+static struct log_target *stderr_target;
+static int daemonize = 0;
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Harald Welte and On-Waves\r\n"
+	"License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static struct sgsn_instance sgsn_inst = {
+	.config_file = "osmo_sgsn.cfg",
+	.cfg = {
+		.gtp_statedir = "./",
+	},
+};
+struct sgsn_instance *sgsn = &sgsn_inst;
+
+/* call-back function for the NS protocol */
+static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
+		      struct msgb *msg, u_int16_t bvci)
+{
+	int rc = 0;
+
+	switch (event) {
+	case GPRS_NS_EVT_UNIT_DATA:
+		/* hand the message into the BSSGP implementation */
+		rc = gprs_bssgp_rcvmsg(msg);
+		break;
+	default:
+		LOGP(DGPRS, LOGL_ERROR, "SGSN: Unknown event %u from NS\n", event);
+		if (msg)
+			talloc_free(msg);
+		rc = -EIO;
+		break;
+	}
+	return rc;
+}
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+		sleep(1);
+		exit(0);
+		break;
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		talloc_report_full(tall_vty_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+/* NSI that BSSGP uses when transmitting on NS */
+extern struct gprs_ns_inst *bssgp_nsi;
+extern void *tall_msgb_ctx;
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OsmoSGSN",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+static void print_help(void)
+{
+	printf("Some useful help...\n");
+	printf("  -h --help\tthis text\n");
+	printf("  -D --daemonize\tFork the process into a background daemon\n");
+	printf("  -d option --debug\tenable Debugging\n");
+	printf("  -s --disable-color\n");
+	printf("  -c --config-file\tThe config file to use\n");
+	printf("  -e --log-level number\tSet a global log level\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"daemonize", 0, 0, 'D'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"log-level", 1, 0, 'e'},
+			{NULL, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:Dc:sTe:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			//print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'c':
+			sgsn_inst.config_file = strdup(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'e':
+			log_set_log_level(stderr_target, atoi(optarg));
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct sockaddr_in sin;
+	int rc;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 0, "osmo_sgsn");
+	tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	signal(SIGPIPE, SIG_IGN);
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	logging_vty_add_cmds();
+        sgsn_vty_init();
+
+	handle_options(argc, argv);
+
+	rate_ctr_init(tall_bsc_ctx);
+	rc = telnet_init(tall_bsc_ctx, &dummy_network, 4245);
+	if (rc < 0)
+		exit(1);
+
+	sgsn_nsi = gprs_ns_instantiate(&sgsn_ns_cb);
+	if (!sgsn_nsi) {
+		LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n");
+		exit(1);
+	}
+	bssgp_nsi = sgsn_inst.cfg.nsi = sgsn_nsi;
+
+	gprs_llc_init("/usr/local/lib/osmocom/crypt/");
+
+	gprs_ns_vty_init(bssgp_nsi);
+	gprs_bssgp_vty_init();
+	gprs_llc_vty_init();
+	gprs_sndcp_vty_init();
+	/* FIXME: register signal handler for SS_NS */
+
+	rc = sgsn_parse_config(sgsn_inst.config_file, &sgsn_inst.cfg);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file\n");
+		exit(2);
+	}
+
+	rc = sgsn_gtp_init(&sgsn_inst);
+	if (rc) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on GTP socket\n");
+		exit(2);
+	}
+
+	rc = gprs_ns_nsip_listen(sgsn_nsi);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen on NSIP socket\n");
+		exit(2);
+	}
+
+	rc = gprs_ns_frgre_listen(sgsn_nsi);
+	if (rc < 0) {
+		LOGP(DGPRS, LOGL_FATAL, "Cannot bind/listen GRE "
+			"socket. Do you have CAP_NET_RAW?\n");
+		exit(2);
+	}
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
diff --git a/src/gprs/sgsn_vty.c b/src/gprs/sgsn_vty.c
new file mode 100644
index 0000000..74669ff
--- /dev/null
+++ b/src/gprs/sgsn_vty.c
@@ -0,0 +1,357 @@
+/*
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+#include <osmocore/rate_ctr.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_04_08_gprs.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+#include <pdp.h>
+
+static struct sgsn_config *g_cfg = NULL;
+
+
+#define GSM48_MAX_APN_LEN	102	/* 10.5.6.1 */
+static char *gprs_apn2str(uint8_t *apn, unsigned int len)
+{
+	static char apnbuf[GSM48_MAX_APN_LEN+1];
+	unsigned int i;
+
+	if (!apn)
+		return "";
+
+	if (len > sizeof(apnbuf)-1)
+		len = sizeof(apnbuf)-1;
+
+	memcpy(apnbuf, apn, len);
+	apnbuf[len] = '\0';
+
+	/* replace the domain name step sizes with dots */
+	while (i < len) {
+		unsigned int step = apnbuf[i];
+		apnbuf[i] = '.';
+		i += step+1;
+	}
+
+	return apnbuf+1;
+}
+
+static char *gprs_pdpaddr2str(uint8_t *pdpa, uint8_t len)
+{
+	static char str[INET6_ADDRSTRLEN + 10];
+
+	if (!pdpa || len < 2)
+		return "none";
+
+	switch (pdpa[0] & 0x0f) {
+	case PDP_TYPE_ORG_IETF:
+		switch (pdpa[1]) {
+		case PDP_TYPE_N_IETF_IPv4:
+			if (len < 2 + 4)
+				break;
+			strcpy(str, "IPv4 ");
+			inet_ntop(AF_INET, pdpa+2, str+5, sizeof(str)-5);
+			return str;
+		case PDP_TYPE_N_IETF_IPv6:
+			if (len < 2 + 8)
+				break;
+			strcpy(str, "IPv6 ");
+			inet_ntop(AF_INET6, pdpa+2, str+5, sizeof(str)-5);
+			return str;
+		default:
+			break;
+		}
+		break;
+	case PDP_TYPE_ORG_ETSI:
+		if (pdpa[1] == PDP_TYPE_N_ETSI_PPP)
+			return "PPP";
+		break;
+	default:
+		break;
+	}
+
+	return "invalid";
+}
+
+static struct cmd_node sgsn_node = {
+	SGSN_NODE,
+	"%s(sgsn)#",
+	1,
+};
+
+static int config_write_sgsn(struct vty *vty)
+{
+	struct sgsn_ggsn_ctx *gctx;
+
+	vty_out(vty, "sgsn%s", VTY_NEWLINE);
+
+	vty_out(vty, " gtp local-ip %s%s",
+		inet_ntoa(g_cfg->gtp_listenaddr.sin_addr), VTY_NEWLINE);
+
+	llist_for_each_entry(gctx, &sgsn_ggsn_ctxts, list) {
+		vty_out(vty, " ggsn %u remote-ip %s%s", gctx->id,
+			inet_ntoa(gctx->remote_addr), VTY_NEWLINE);
+		vty_out(vty, " ggsn %u gtp-version %u%s", gctx->id,
+			gctx->gtp_version, VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define SGSN_STR	"Configure the SGSN"
+
+DEFUN(cfg_sgsn, cfg_sgsn_cmd,
+	"sgsn",
+	SGSN_STR)
+{
+	vty->node = SGSN_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_sgsn_bind_addr, cfg_sgsn_bind_addr_cmd,
+	"gtp local-ip A.B.C.D",
+	"GTP Parameters\n"
+	"Set the IP address for the local GTP bind\n")
+{
+	inet_aton(argv[0], &g_cfg->gtp_listenaddr.sin_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ggsn_remote_ip, cfg_ggsn_remote_ip_cmd,
+	"ggsn <0-255> remote-ip A.B.C.D",
+	"")
+{
+	uint32_t id = atoi(argv[0]);
+	struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id);
+
+	inet_aton(argv[1], &ggc->remote_addr);
+
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_ggsn_remote_port, cfg_ggsn_remote_port_cmd,
+	"ggsn <0-255> remote-port <0-65535>",
+	"")
+{
+	uint32_t id = atoi(argv[0]);
+	struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id);
+	uint16_t port = atoi(argv[1]);
+
+}
+#endif
+
+DEFUN(cfg_ggsn_gtp_version, cfg_ggsn_gtp_version_cmd,
+	"ggsn <0-255> gtp-version (0|1)",
+	"")
+{
+	uint32_t id = atoi(argv[0]);
+	struct sgsn_ggsn_ctx *ggc = sgsn_ggsn_ctx_find_alloc(id);
+
+	if (atoi(argv[1]))
+		ggc->gtp_version = 1;
+	else
+		ggc->gtp_version = 0;
+
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_apn_ggsn, cfg_apn_ggsn_cmd,
+	"apn APNAME ggsn <0-255>",
+	"")
+{
+	struct apn_ctx **
+}
+#endif
+
+const struct value_string gprs_mm_st_strs[] = {
+	{ GMM_DEREGISTERED, "DEREGISTERED" },
+	{ GMM_COMMON_PROC_INIT, "COMMON PROCEDURE (INIT)" },
+	{ GMM_REGISTERED_NORMAL, "REGISTERED (NORMAL)" },
+	{ GMM_REGISTERED_SUSPENDED, "REGISTERED (SUSPENDED)" },
+	{ GMM_DEREGISTERED_INIT, "DEREGISTERED (INIT)" },
+	{ 0, NULL }
+};
+
+static void vty_dump_pdp(struct vty *vty, const char *pfx,
+			 struct sgsn_pdp_ctx *pdp)
+{
+	vty_out(vty, "%sPDP Context IMSI: %s, SAPI: %u, NSAPI: %u%s",
+		pfx, pdp->mm->imsi, pdp->sapi, pdp->nsapi, VTY_NEWLINE);
+	vty_out(vty, "%s  APN: %s%s", pfx,
+		gprs_apn2str(pdp->lib->apn_use.v, pdp->lib->apn_use.l),
+		VTY_NEWLINE);
+	vty_out(vty, "%s  PDP Address: %s%s", pfx,
+		gprs_pdpaddr2str(pdp->lib->eua.v, pdp->lib->eua.l),
+		VTY_NEWLINE);
+	vty_out_rate_ctr_group(vty, " ", pdp->ctrg);
+}
+
+static void vty_dump_mmctx(struct vty *vty, const char *pfx,
+			   struct sgsn_mm_ctx *mm, int pdp)
+{
+	vty_out(vty, "%sMM Context for IMSI %s, IMEI %s, P-TMSI %08x%s",
+		pfx, mm->imsi, mm->imei, mm->p_tmsi, VTY_NEWLINE);
+	vty_out(vty, "%s  MSISDN: %s, TLLI: %08x%s", pfx, mm->msisdn,
+		mm->tlli, VTY_NEWLINE);
+	vty_out(vty, "%s  MM State: %s, Routeing Area: %u-%u-%u-%u, "
+		"Cell ID: %u%s", pfx,
+		get_value_string(gprs_mm_st_strs, mm->mm_state),
+		mm->ra.mcc, mm->ra.mnc, mm->ra.lac, mm->ra.rac,
+		mm->cell_id, VTY_NEWLINE);
+
+	vty_out_rate_ctr_group(vty, " ", mm->ctrg);
+
+	if (pdp) {
+		struct sgsn_pdp_ctx *pdp;
+
+		llist_for_each_entry(pdp, &mm->pdp_list, list)
+			vty_dump_pdp(vty, "  ", pdp);
+	}
+}
+
+DEFUN(show_sgsn, show_sgsn_cmd, "show sgsn",
+      SHOW_STR "Display information about the SGSN")
+{
+	/* FIXME: statistics */
+	return CMD_SUCCESS;
+}
+
+#define MMCTX_STR "MM Context\n"
+#define INCLUDE_PDP_STR "Include PDP Context Information\n"
+
+#if 0
+DEFUN(show_mmctx_tlli, show_mmctx_tlli_cmd,
+	"show mm-context tlli HEX [pdp]",
+	SHOW_STR MMCTX_STR "Identify by TLLI\n" "TLLI\n" INCLUDE_PDP_STR)
+{
+	uint32_t tlli;
+	struct sgsn_mm_ctx *mm;
+
+	tlli = strtoul(argv[0], NULL, 16);
+	mm = sgsn_mm_ctx_by_tlli(tlli);
+	if (!mm) {
+		vty_out(vty, "No MM context for TLLI %08x%s",
+			tlli, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0);
+	return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(swow_mmctx_imsi, show_mmctx_imsi_cmd,
+	"show mm-context imsi IMSI [pdp]",
+	SHOW_STR MMCTX_STR "Identify by IMSI\n" "IMSI of the MM Context\n"
+	INCLUDE_PDP_STR)
+{
+	struct sgsn_mm_ctx *mm;
+
+	mm = sgsn_mm_ctx_by_imsi(argv[0]);
+	if (!mm) {
+		vty_out(vty, "No MM context for IMSI %s%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty_dump_mmctx(vty, "", mm, argv[1] ? 1 : 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(swow_mmctx_all, show_mmctx_all_cmd,
+	"show mm-context all [pdp]",
+	SHOW_STR MMCTX_STR "All MM Contexts\n" INCLUDE_PDP_STR)
+{
+	struct sgsn_mm_ctx *mm;
+
+	llist_for_each_entry(mm, &sgsn_mm_ctxts, list)
+		vty_dump_mmctx(vty, "", mm, argv[0] ? 1 : 0);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_ggsn, show_ggsn_cmd,
+	"show ggsn",
+	"")
+{
+
+}
+
+DEFUN(show_pdpctx_all, show_pdpctx_all_cmd,
+	"show pdp-context all",
+	SHOW_STR "Display information on PDP Context\n")
+{
+	struct sgsn_pdp_ctx *pdp;
+
+	llist_for_each_entry(pdp, &sgsn_pdp_ctxts, g_list)
+		vty_dump_pdp(vty, "", pdp);
+
+	return CMD_SUCCESS;
+}
+
+int sgsn_vty_init(void)
+{
+	install_element_ve(&show_sgsn_cmd);
+	//install_element_ve(&show_mmctx_tlli_cmd);
+	install_element_ve(&show_mmctx_imsi_cmd);
+	install_element_ve(&show_mmctx_all_cmd);
+	install_element_ve(&show_pdpctx_all_cmd);
+
+	install_element(CONFIG_NODE, &cfg_sgsn_cmd);
+	install_node(&sgsn_node, config_write_sgsn);
+	install_default(SGSN_NODE);
+	install_element(SGSN_NODE, &ournode_exit_cmd);
+	install_element(SGSN_NODE, &ournode_end_cmd);
+	install_element(SGSN_NODE, &cfg_sgsn_bind_addr_cmd);
+	install_element(SGSN_NODE, &cfg_ggsn_remote_ip_cmd);
+	//install_element(SGSN_NODE, &cfg_ggsn_remote_port_cmd);
+	install_element(SGSN_NODE, &cfg_ggsn_gtp_version_cmd);
+
+	return 0;
+}
+
+int sgsn_parse_config(const char *config_file, struct sgsn_config *cfg)
+{
+	int rc;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file, NULL);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am
new file mode 100644
index 0000000..144cca1
--- /dev/null
+++ b/src/ipaccess/Makefile.am
@@ -0,0 +1,21 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = ipaccess-find ipaccess-config ipaccess-proxy
+
+ipaccess_find_SOURCES = ipaccess-find.c
+
+ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c network_listen.c
+
+# FIXME: resolve the bogus dependencies patched around here:
+ipaccess_config_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libmsc/libmsc.a \
+			$(top_builddir)/src/libabis/libabis.a \
+			$(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libtrau/libtrau.a \
+			$(top_builddir)/src/libcommon/libcommon.a \
+			-ldl -ldbi $(LIBCRYPT)
+
+ipaccess_proxy_SOURCES = ipaccess-proxy.c
+ipaccess_proxy_LDADD = $(top_builddir)/src/libcommon/libcommon.a
diff --git a/src/ipaccess/Makefile.in b/src/ipaccess/Makefile.in
new file mode 100644
index 0000000..864fa31
--- /dev/null
+++ b/src/ipaccess/Makefile.in
@@ -0,0 +1,520 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = ipaccess-find$(EXEEXT) ipaccess-config$(EXEEXT) \
+	ipaccess-proxy$(EXEEXT)
+subdir = src/ipaccess
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_ipaccess_config_OBJECTS = ipaccess-config.$(OBJEXT) \
+	ipaccess-firmware.$(OBJEXT) network_listen.$(OBJEXT)
+ipaccess_config_OBJECTS = $(am_ipaccess_config_OBJECTS)
+ipaccess_config_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libabis/libabis.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a
+am_ipaccess_find_OBJECTS = ipaccess-find.$(OBJEXT)
+ipaccess_find_OBJECTS = $(am_ipaccess_find_OBJECTS)
+ipaccess_find_LDADD = $(LDADD)
+am_ipaccess_proxy_OBJECTS = ipaccess-proxy.$(OBJEXT)
+ipaccess_proxy_OBJECTS = $(am_ipaccess_proxy_OBJECTS)
+ipaccess_proxy_DEPENDENCIES =  \
+	$(top_builddir)/src/libcommon/libcommon.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(ipaccess_config_SOURCES) $(ipaccess_find_SOURCES) \
+	$(ipaccess_proxy_SOURCES)
+DIST_SOURCES = $(ipaccess_config_SOURCES) $(ipaccess_find_SOURCES) \
+	$(ipaccess_proxy_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+ipaccess_find_SOURCES = ipaccess-find.c
+ipaccess_config_SOURCES = ipaccess-config.c ipaccess-firmware.c network_listen.c
+
+# FIXME: resolve the bogus dependencies patched around here:
+ipaccess_config_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libmsc/libmsc.a \
+			$(top_builddir)/src/libabis/libabis.a \
+			$(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libtrau/libtrau.a \
+			$(top_builddir)/src/libcommon/libcommon.a \
+			-ldl -ldbi $(LIBCRYPT)
+
+ipaccess_proxy_SOURCES = ipaccess-proxy.c
+ipaccess_proxy_LDADD = $(top_builddir)/src/libcommon/libcommon.a
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/ipaccess/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/ipaccess/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+ipaccess-config$(EXEEXT): $(ipaccess_config_OBJECTS) $(ipaccess_config_DEPENDENCIES) 
+	@rm -f ipaccess-config$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(ipaccess_config_OBJECTS) $(ipaccess_config_LDADD) $(LIBS)
+ipaccess-find$(EXEEXT): $(ipaccess_find_OBJECTS) $(ipaccess_find_DEPENDENCIES) 
+	@rm -f ipaccess-find$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(ipaccess_find_OBJECTS) $(ipaccess_find_LDADD) $(LIBS)
+ipaccess-proxy$(EXEEXT): $(ipaccess_proxy_OBJECTS) $(ipaccess_proxy_DEPENDENCIES) 
+	@rm -f ipaccess-proxy$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(ipaccess_proxy_OBJECTS) $(ipaccess_proxy_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-find.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-firmware.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess-proxy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_listen.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c
new file mode 100644
index 0000000..d02faea
--- /dev/null
+++ b/src/ipaccess/ipaccess-config.c
@@ -0,0 +1,865 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocore/select.h>
+#include <osmocore/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/network_listen.h>
+#include <osmocore/talloc.h>
+
+static struct gsm_network *gsmnet;
+
+static int net_listen_testnr;
+static int restart;
+static char *prim_oml_ip;
+static char *bts_ip_addr, *bts_ip_mask, *bts_ip_gw;
+static char *unit_id;
+static u_int16_t nv_flags;
+static u_int16_t nv_mask;
+static char *software = NULL;
+static int sw_load_state = 0;
+static int oml_state = 0;
+static int dump_files = 0;
+static char *firmware_analysis = NULL;
+static int found_trx = 0;
+
+struct sw_load {
+	u_int8_t file_id[255];
+	u_int8_t file_id_len;
+
+	u_int8_t file_version[255];
+	u_int8_t file_version_len;
+};
+
+static void *tall_ctx_config = NULL;
+static struct sw_load *sw_load1 = NULL;
+static struct sw_load *sw_load2 = NULL;
+
+/*
+static u_int8_t prim_oml_attr[] = { 0x95, 0x00, 7, 0x88, 192, 168, 100, 11, 0x00, 0x00 };
+static u_int8_t unit_id_attr[] = { 0x91, 0x00, 9, '2', '3', '4', '2', '/' , '0', '/', '0', 0x00 };
+*/
+
+/*
+ * Callback function for NACK on the OML NM
+ *
+ * Currently we send the config requests but don't check the
+ * result. The nanoBTS will send us a NACK when we did something the
+ * BTS didn't like.
+ */
+static int ipacc_msg_nack(u_int8_t mt)
+{
+	fprintf(stderr, "Failure to set attribute. This seems fatal\n");
+	exit(-1);
+	return 0;
+}
+
+static void check_restart_or_exit(struct gsm_bts_trx *trx)
+{
+	if (restart) {
+		abis_nm_ipaccess_restart(trx);
+	} else {
+		exit(0);
+	}
+}
+
+static int ipacc_msg_ack(u_int8_t mt, struct gsm_bts_trx *trx)
+{
+	if (sw_load_state == 1) {
+		fprintf(stderr, "The new software is activaed.\n");
+		check_restart_or_exit(trx);
+	} else if (oml_state == 1) {
+		fprintf(stderr, "Set the NV Attributes.\n");
+		check_restart_or_exit(trx);
+	}
+
+	return 0;
+}
+
+static const uint8_t phys_conf_min[] = { 0x02 };
+
+static uint16_t build_physconf(uint8_t *physconf_buf, const struct rxlev_stats *st)
+{
+	uint16_t *whitelist = (uint16_t *) (physconf_buf + 4);
+	int num_arfcn;
+	unsigned int arfcnlist_size;
+
+	/* Create whitelist from rxlevels */
+	physconf_buf[0] = phys_conf_min[0];
+	physconf_buf[1] = NM_IPAC_EIE_ARFCN_WHITE;
+	num_arfcn = ipac_rxlevstat2whitelist(whitelist, st, 0, 100);
+	arfcnlist_size = num_arfcn * 2;
+	*((uint16_t *) (physconf_buf+2)) = htons(arfcnlist_size);
+	DEBUGP(DNM, "physconf_buf (%s)\n", hexdump(physconf_buf, arfcnlist_size+4));
+	return arfcnlist_size+4;
+}
+
+static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts_trx *trx;
+	uint8_t physconf_buf[2*NUM_ARFCNS+16];
+	uint16_t physconf_len;
+
+	switch (signal) {
+	case S_IPAC_NWL_COMPLETE:
+		trx = signal_data;
+		DEBUGP(DNM, "received S_IPAC_NWL_COMPLETE signal\n");
+		switch (trx->ipaccess.test_nr) {
+		case NM_IPACC_TESTNO_CHAN_USAGE:
+			/* Dump RxLev results */
+			//rxlev_stat_dump(&trx->ipaccess.rxlev_stat);
+			/* Create whitelist from results */
+			physconf_len = build_physconf(physconf_buf,
+						      &trx->ipaccess.rxlev_stat);
+			/* Start next test abbout BCCH channel usage */
+			ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_CHAN_USAGE,
+					    physconf_buf, physconf_len);
+			break;
+		case NM_IPACC_TESTNO_BCCH_CHAN_USAGE:
+			/* Dump BCCH RxLev results */
+			//rxlev_stat_dump(&trx->ipaccess.rxlev_stat);
+			/* Create whitelist from results */
+			physconf_len = build_physconf(physconf_buf,
+						      &trx->ipaccess.rxlev_stat);
+			/* Start next test about BCCH info */
+			ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_INFO,
+					    physconf_buf, physconf_len);
+			break;
+		case NM_IPACC_TESTNO_BCCH_INFO:
+#if 0
+			/* re-start full process with CHAN_USAGE */
+			DEBUGP(DNM, "starting next test cycle\n");
+			ipac_nwl_test_start(trx, net_listen_testnr, phys_conf_min,
+					    sizeof(phys_conf_min));
+#else
+			exit(0);
+#endif
+			break;
+		}
+		break;
+	}
+	return 0;
+}
+
+static int nm_state_event(int evt, u_int8_t obj_class, void *obj,
+			  struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+			  struct abis_om_obj_inst *obj_inst);
+
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	struct ipacc_ack_signal_data *ipacc_data;
+	struct nm_statechg_signal_data *nsd;
+
+	switch (signal) {
+	case S_NM_IPACC_NACK:
+		ipacc_data = signal_data;
+		return ipacc_msg_nack(ipacc_data->msg_type);
+	case S_NM_IPACC_ACK:
+		ipacc_data = signal_data;
+		return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->trx);
+	case S_NM_IPACC_RESTART_ACK:
+		printf("The BTS has acked the restart. Exiting.\n");
+		exit(0);
+		break;
+	case S_NM_IPACC_RESTART_NACK:
+		printf("The BTS has nacked the restart. Exiting.\n");
+		exit(0);
+		break;
+	case S_NM_STATECHG_OPER:
+	case S_NM_STATECHG_ADM:
+		nsd = signal_data;
+		nm_state_event(signal, nsd->obj_class, nsd->obj, nsd->old_state,
+				nsd->new_state, nsd->obj_inst);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* callback function passed to the ABIS OML code */
+static int percent;
+static int percent_old;
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *_msg,
+		       void *data, void *param)
+{
+	struct msgb *msg;
+	struct gsm_bts_trx *trx;
+
+	if (hook != GSM_HOOK_NM_SWLOAD)
+		return 0;
+
+	trx = (struct gsm_bts_trx *) data;
+
+	switch (event) {
+	case NM_MT_LOAD_INIT_ACK:
+		fprintf(stdout, "Software Load Initiate ACK\n");
+		break;
+	case NM_MT_LOAD_INIT_NACK:
+		fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+		exit(5);
+		break;
+	case NM_MT_LOAD_END_ACK:
+		fprintf(stderr, "LOAD END ACK...");
+		/* now make it the default */
+		sw_load_state = 1;
+
+		msg = msgb_alloc(1024, "sw: nvattr");
+		msg->l2h = msgb_put(msg, 3);
+		msg->l3h = &msg->l2h[3];
+
+		/* activate software */
+		if (sw_load1) {
+			msgb_v_put(msg, NM_ATT_SW_DESCR);
+			msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load1->file_id_len, sw_load1->file_id);
+			msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load1->file_version_len,
+					sw_load1->file_version);
+		}
+
+		if (sw_load2) {
+			msgb_v_put(msg, NM_ATT_SW_DESCR);
+			msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw_load2->file_id_len, sw_load2->file_id);
+			msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw_load2->file_version_len,
+					sw_load2->file_version);
+		}
+
+		/* fill in the data */
+		msg->l2h[0] = NM_ATT_IPACC_CUR_SW_CFG;
+		msg->l2h[1] = msgb_l3len(msg) >> 8;
+		msg->l2h[2] = msgb_l3len(msg) & 0xff;
+		printf("Foo l2h: %p l3h: %p... length l2: %u  l3: %u\n", msg->l2h, msg->l3h, msgb_l2len(msg), msgb_l3len(msg));
+		abis_nm_ipaccess_set_nvattr(trx, msg->l2h, msgb_l2len(msg));
+		msgb_free(msg);
+		break;
+	case NM_MT_LOAD_END_NACK:
+		fprintf(stderr, "ERROR: Software Load End NACK\n");
+		exit(3);
+		break;
+	case NM_MT_ACTIVATE_SW_NACK:
+		fprintf(stderr, "ERROR: Activate Software NACK\n");
+		exit(4);
+		break;
+	case NM_MT_ACTIVATE_SW_ACK:
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+		percent = abis_nm_software_load_status(trx->bts);
+		if (percent > percent_old)
+			printf("Software Download Progress: %d%%\n", percent);
+		percent_old = percent;
+		break;
+	case NM_MT_LOAD_ABORT:
+		fprintf(stderr, "ERROR: Load aborted by the BTS.\n");
+		exit(6);
+		break;
+	}
+	return 0;
+}
+
+static void nv_put_ip_if_cfg(struct msgb *nmsg, uint32_t ip, uint32_t mask)
+{
+	msgb_put_u8(nmsg, NM_ATT_IPACC_IP_IF_CFG);
+
+	msgb_put_u32(nmsg, ip);
+	msgb_put_u32(nmsg, mask);
+}
+
+static void nv_put_gw_cfg(struct msgb *nmsg, uint32_t addr, uint32_t mask, uint32_t gw)
+{
+	msgb_put_u8(nmsg, NM_ATT_IPACC_IP_GW_CFG);
+	msgb_put_u32(nmsg, addr);
+	msgb_put_u32(nmsg, mask);
+	msgb_put_u32(nmsg, gw);
+}
+
+static void nv_put_unit_id(struct msgb *nmsg, const char *unit_id)
+{
+	msgb_tl16v_put(nmsg, NM_ATT_IPACC_UNIT_ID, strlen(unit_id)+1,
+			(const uint8_t *)unit_id);
+}
+
+static void nv_put_prim_oml(struct msgb *nmsg, uint32_t ip, uint16_t port)
+{
+	int len;
+
+	/* 0x88 + IP + port */
+	len = 1 + sizeof(ip) + sizeof(port);
+
+	msgb_put_u8(nmsg, NM_ATT_IPACC_PRIM_OML_CFG_LIST);
+	msgb_put_u16(nmsg, len);
+
+	msgb_put_u8(nmsg, 0x88);
+
+	/* IP address */
+	msgb_put_u32(nmsg, ip);
+
+	/* port number */
+	msgb_put_u16(nmsg, port);
+}
+
+static void nv_put_flags(struct msgb *nmsg, uint16_t nv_flags, uint16_t nv_mask)
+{
+	msgb_put_u8(nmsg, NM_ATT_IPACC_NV_FLAGS);
+	msgb_put_u16(nmsg, sizeof(nv_flags) + sizeof(nv_mask));
+	msgb_put_u8(nmsg, nv_flags & 0xff);
+	msgb_put_u8(nmsg, nv_mask & 0xff);
+	msgb_put_u8(nmsg, nv_flags >> 8);
+	msgb_put_u8(nmsg, nv_mask >> 8);
+}
+
+/* human-readable names for the ip.access nanoBTS NVRAM Flags */
+static const struct value_string ipa_nvflag_strs[] = {
+	{ 0x0001, "static-ip" },
+	{ 0x0002, "static-gw" },
+	{ 0x0004, "no-dhcp-vsi" },
+	{ 0x0008, "dhcp-enabled" },
+	{ 0x0040, "led-disabled" },
+	{ 0x0100, "secondary-oml-enabled" },
+	{ 0x0200, "diag-enabled" },
+	{ 0x0400, "cli-enabled" },
+	{ 0x0800, "http-enabled" },
+	{ 0x1000, "post-enabled" },
+	{ 0x2000, "snmp-enabled" },
+	{ 0, NULL }
+};
+
+/* set the flags in flags/mask according to a string-identified flag and 'enable' */
+static int ipa_nvflag_set(uint16_t *flags, uint16_t *mask, const char *name, int en)
+{
+	int rc;
+	rc = get_string_value(ipa_nvflag_strs, name);
+	if (rc < 0)
+		return rc;
+
+	*mask |= rc;
+	if (en)
+		*flags |= rc;
+	else
+		*flags &= ~rc;
+
+	return 0;
+}
+
+static void bootstrap_om(struct gsm_bts_trx *trx)
+{
+	struct msgb *nmsg = msgb_alloc(1024, "nested msgb");
+	int need_to_set_attr = 0;
+	int len;
+
+	printf("OML link established using TRX %d\n", trx->nr);
+
+	if (unit_id) {
+		len = strlen(unit_id);
+		if (len > nmsg->data_len-10)
+			goto out_err;
+		printf("setting Unit ID to '%s'\n", unit_id);
+		nv_put_unit_id(nmsg, unit_id);
+		need_to_set_attr = 1;
+	}
+	if (prim_oml_ip) {
+		struct in_addr ia;
+
+		if (!inet_aton(prim_oml_ip, &ia)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				prim_oml_ip);
+			goto out_err;
+		}
+
+		printf("setting primary OML link IP to '%s'\n", inet_ntoa(ia));
+		nv_put_prim_oml(nmsg, ntohl(ia.s_addr), 0);
+		need_to_set_attr = 1;
+	}
+	if (nv_mask) {
+		printf("setting NV Flags/Mask to 0x%04x/0x%04x\n",
+			nv_flags, nv_mask);
+		nv_put_flags(nmsg, nv_flags, nv_mask);
+		need_to_set_attr = 1;
+	}
+	if (bts_ip_addr && bts_ip_mask) {
+		struct in_addr ia_addr, ia_mask;
+
+		if (!inet_aton(bts_ip_addr, &ia_addr)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				bts_ip_addr);
+			goto out_err;
+		}
+
+		if (!inet_aton(bts_ip_mask, &ia_mask)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				bts_ip_mask);
+			goto out_err;
+		}
+
+		printf("setting static IP Address/Mask\n");
+		nv_put_ip_if_cfg(nmsg, ntohl(ia_addr.s_addr), ntohl(ia_mask.s_addr));
+		need_to_set_attr = 1;
+	}
+	if (bts_ip_gw) {
+		struct in_addr ia_gw;
+
+		if (!inet_aton(bts_ip_gw, &ia_gw)) {
+			fprintf(stderr, "invalid IP address: %s\n",
+				bts_ip_gw);
+			goto out_err;
+		}
+
+		printf("setting static IP Gateway\n");
+		/* we only set the default gateway with zero addr/mask */
+		nv_put_gw_cfg(nmsg, 0, 0, ntohl(ia_gw.s_addr));
+		need_to_set_attr = 1;
+	}
+
+	if (need_to_set_attr) {
+		abis_nm_ipaccess_set_nvattr(trx, nmsg->head, nmsg->len);
+		oml_state = 1;
+	}
+
+	if (restart && !prim_oml_ip && !software) {
+		printf("restarting BTS\n");
+		abis_nm_ipaccess_restart(trx);
+	}
+
+out_err:
+	msgb_free(nmsg);
+}
+
+static int nm_state_event(int evt, u_int8_t obj_class, void *obj,
+			  struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+			  struct abis_om_obj_inst *obj_inst)
+{
+	if (obj_class == NM_OC_BASEB_TRANSC) {
+		if (!found_trx && obj_inst->trx_nr != 0xff) {
+			struct gsm_bts_trx *trx = container_of(obj, struct gsm_bts_trx, bb_transc);
+			bootstrap_om(trx);
+			found_trx = 1;
+		}
+	} else if (evt == S_NM_STATECHG_OPER &&
+	    obj_class == NM_OC_RADIO_CARRIER &&
+	    new_state->availability == 3) {
+		struct gsm_bts_trx *trx = obj;
+
+		if (net_listen_testnr)
+			ipac_nwl_test_start(trx, net_listen_testnr,
+					    phys_conf_min, sizeof(phys_conf_min));
+		else if (software) {
+			int rc;
+			printf("Attempting software upload with '%s'\n", software);
+			rc = abis_nm_software_load(trx->bts, trx->nr, software, 19, 0, swload_cbfn, trx);
+			if (rc < 0) {
+				fprintf(stderr, "Failed to start software load\n");
+				exit(-3);
+			}
+		}
+	}
+	return 0;
+}
+
+static struct sw_load *create_swload(struct sdp_header *header)
+{
+	struct sw_load *load;
+
+	load = talloc_zero(tall_ctx_config, struct sw_load);
+
+	strncpy((char *)load->file_id, header->firmware_info.sw_part, 20);
+	load->file_id_len = strlen(header->firmware_info.sw_part) + 1;
+
+	strncpy((char *)load->file_version, header->firmware_info.version, 20);
+	load->file_version_len = strlen(header->firmware_info.version) + 1;
+
+	return load;
+}
+
+static int find_sw_load_params(const char *filename)
+{
+	struct stat stat;
+	struct sdp_header *header;
+	struct llist_head *entry;
+	int fd;
+	void *tall_firm_ctx = 0;
+
+	entry = talloc_zero(tall_firm_ctx, struct llist_head);
+	INIT_LLIST_HEAD(entry);
+
+	fd = open(filename, O_RDONLY);
+	if (!fd) {
+		perror("nada");
+		return -1;
+	}
+
+	/* verify the file */
+	if (fstat(fd, &stat) == -1) {
+		perror("Can not stat the file");
+		return -1;
+	}
+
+	ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+	if (close(fd) != 0) {
+		perror("Close failed.\n");
+		return -1;
+	}
+
+	/* try to find what we are looking for */
+	llist_for_each_entry(header, entry, entry) {
+		if (ntohs(header->firmware_info.more_more_magic) == 0x1000) {
+			sw_load1 = create_swload(header);
+		} else if (ntohs(header->firmware_info.more_more_magic) == 0x2001) {
+			sw_load2 = create_swload(header);
+		}
+	}
+
+	if (!sw_load1 || !sw_load2) {
+		fprintf(stderr, "Did not find data.\n");
+		talloc_free(tall_firm_ctx);
+		return -1;
+        }
+
+	talloc_free(tall_firm_ctx);
+	return 0;
+}
+
+static void dump_entry(struct sdp_header_item *sub_entry, int part, int fd)
+{
+	int out_fd;
+	int copied;
+	char filename[4096];
+	off_t target;
+
+	if (!dump_files)
+		return;
+
+	if (sub_entry->header_entry.something1 == 0)
+		return;
+
+	snprintf(filename, sizeof(filename), "part.%d", part++);
+	out_fd = open(filename, O_WRONLY | O_CREAT, 0660);
+	if (out_fd < 0) {
+		perror("Can not dump firmware");
+		return;
+	}
+
+	target = sub_entry->absolute_offset + ntohl(sub_entry->header_entry.start) + 4;
+	if (lseek(fd, target, SEEK_SET) != target) {
+		perror("seek failed");
+		close(out_fd);
+		return;
+	}
+
+	for (copied = 0; copied < ntohl(sub_entry->header_entry.length); ++copied) {
+		char c;
+		if (read(fd, &c, sizeof(c)) != sizeof(c)) {
+			perror("copy failed");
+			break;
+		}
+
+		if (write(out_fd, &c, sizeof(c)) != sizeof(c)) {
+			perror("write failed");
+			break;
+		}
+	}
+
+	close(out_fd);
+}
+
+static void analyze_firmware(const char *filename)
+{
+	struct stat stat;
+	struct sdp_header *header;
+	struct sdp_header_item *sub_entry;
+	struct llist_head *entry;
+	int fd;
+	void *tall_firm_ctx = 0;
+	int part = 0;
+
+	entry = talloc_zero(tall_firm_ctx, struct llist_head);
+	INIT_LLIST_HEAD(entry);
+
+	printf("Opening possible firmware '%s'\n", filename);
+	fd = open(filename, O_RDONLY);
+	if (!fd) {
+		perror("nada");
+		return;
+	}
+
+	/* verify the file */
+	if (fstat(fd, &stat) == -1) {
+		perror("Can not stat the file");
+		return;
+	}
+
+	ipaccess_analyze_file(fd, stat.st_size, 0, entry);
+
+	llist_for_each_entry(header, entry, entry) {
+		printf("Printing header information:\n");
+		printf("more_more_magic: 0x%x\n", ntohs(header->firmware_info.more_more_magic));
+		printf("header_length: %u\n", ntohl(header->firmware_info.header_length));
+		printf("file_length: %u\n", ntohl(header->firmware_info.file_length));
+		printf("sw_part: %.20s\n", header->firmware_info.sw_part);
+		printf("text1: %.64s\n", header->firmware_info.text1);
+		printf("time: %.12s\n", header->firmware_info.time);
+		printf("date: %.14s\n", header->firmware_info.date);
+		printf("text2: %.10s\n", header->firmware_info.text2);
+		printf("version: %.20s\n", header->firmware_info.version);
+		printf("subitems...\n");
+
+		llist_for_each_entry(sub_entry, &header->header_list, entry) {
+			printf("\tsomething1: %u\n", sub_entry->header_entry.something1);
+			printf("\ttext1: %.64s\n", sub_entry->header_entry.text1);
+			printf("\ttime: %.12s\n", sub_entry->header_entry.time);
+			printf("\tdate: %.14s\n", sub_entry->header_entry.date);
+			printf("\ttext2: %.10s\n", sub_entry->header_entry.text2);
+			printf("\tversion: %.20s\n", sub_entry->header_entry.version);
+			printf("\tlength: %u\n", ntohl(sub_entry->header_entry.length));
+			printf("\taddr1: 0x%x\n", ntohl(sub_entry->header_entry.addr1));
+			printf("\taddr2: 0x%x\n", ntohl(sub_entry->header_entry.addr2));
+			printf("\tstart: 0x%x\n", ntohl(sub_entry->header_entry.start));
+			printf("\tabs. offset: 0x%lx\n", sub_entry->absolute_offset);
+			printf("\n\n");
+
+			dump_entry(sub_entry, part++, fd);
+		}
+		printf("\n\n");
+	}
+
+	if (close(fd) != 0) {
+		perror("Close failed.\n");
+		return;
+	}
+
+	talloc_free(tall_firm_ctx);
+}
+
+static void print_usage(void)
+{
+	printf("Usage: ipaccess-config\n");
+}
+
+static void print_help(void)
+{
+#if 0
+	printf("Commmands for reading from the BTS:\n");
+	printf("  -D --dump\t\t\tDump the BTS configuration\n");
+	printf("\n");
+#endif
+	printf("Commmands for writing to the BTS:\n");
+	printf("  -u --unit-id UNIT_ID\t\tSet the Unit ID of the BTS\n");
+	printf("  -o --oml-ip IP\t\tSet primary OML IP (IP of your BSC)\n");
+	printf("  -i --ip-address IP/MASK\tSet static IP address + netmask of BTS\n");
+	printf("  -g --ip-gateway IP\t\tSet static IP gateway of BTS\n");
+	printf("  -r --restart\t\t\tRestart the BTS (after other operations)\n");
+	printf("  -n --nvram-flags FLAGS/MASK\tSet NVRAM attributes\n");
+	printf("  -S --nvattr-set FLAG\tSet one additional NVRAM attribute\n");
+	printf("  -U --nvattr-unset FLAG\tSet one additional NVRAM attribute\n");
+	printf("  -l --listen TESTNR\t\tPerform specified test number\n");
+	printf("  -s --stream-id ID\t\tSet the IPA Stream Identifier for OML\n");
+	printf("  -d --software FIRMWARE\tDownload firmware into BTS\n");
+	printf("\n");
+	printf("Miscellaneous commands:\n");
+	printf("  -h --help\t\t\tthis text\n");
+	printf("  -f --firmware FIRMWARE\tProvide firmware information\n");
+	printf("  -w --write-firmware\t\tThis will dump the firmware parts to the filesystem. Use with -f.\n");
+}
+
+extern void bts_model_nanobts_init();
+
+int main(int argc, char **argv)
+{
+	struct gsm_bts *bts;
+	struct sockaddr_in sin;
+	int rc, option_index = 0, stream_id = 0xff;
+	struct log_target *stderr_target;
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	log_set_log_level(stderr_target, 0);
+	log_parse_category_mask(stderr_target, "DNM,0");
+	bts_model_nanobts_init();
+
+	printf("ipaccess-config (C) 2009-2010 by Harald Welte and others\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	while (1) {
+		int c;
+		unsigned long ul;
+		char *slash;
+		static struct option long_options[] = {
+			{ "unit-id", 1, 0, 'u' },
+			{ "oml-ip", 1, 0, 'o' },
+			{ "ip-address", 1, 0, 'i' },
+			{ "ip-gateway", 1, 0, 'g' },
+			{ "restart", 0, 0, 'r' },
+			{ "nvram-flags", 1, 0, 'n' },
+			{ "nvattr-set", 1, 0, 'S' },
+			{ "nvattr-unset", 1, 0, 'U' },
+			{ "help", 0, 0, 'h' },
+			{ "listen", 1, 0, 'l' },
+			{ "stream-id", 1, 0, 's' },
+			{ "software", 1, 0, 'd' },
+			{ "firmware", 1, 0, 'f' },
+			{ "write-firmware", 0, 0, 'w' },
+			{ "disable-color", 0, 0, 'c'},
+			{ 0, 0, 0, 0 },
+		};
+
+		c = getopt_long(argc, argv, "u:o:i:g:rn:S:U:l:hs:d:f:wc", long_options,
+				&option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'u':
+			unit_id = optarg;
+			break;
+		case 'o':
+			prim_oml_ip = optarg;
+			break;
+		case 'i':
+			slash = strchr(optarg, '/');
+			if (!slash)
+				exit(2);
+			bts_ip_addr = optarg;
+			*slash = 0;
+			bts_ip_mask = slash+1;
+			break;
+		case 'g':
+			bts_ip_gw = optarg;
+			break;
+		case 'r':
+			restart = 1;
+			break;
+		case 'n':
+			slash = strchr(optarg, '/');
+			if (!slash)
+				exit(2);
+			ul = strtoul(optarg, NULL, 16);
+			nv_flags = ul & 0xffff;
+			ul = strtoul(slash+1, NULL, 16);
+			nv_mask = ul & 0xffff;
+			break;
+		case 'S':
+			if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 1) < 0)
+				exit(2);
+			break;
+		case 'U':
+			if (ipa_nvflag_set(&nv_flags, &nv_mask, optarg, 0) < 0)
+				exit(2);
+			break;
+		case 'l':
+			net_listen_testnr = atoi(optarg);
+			break;
+		case 's':
+			stream_id = atoi(optarg);
+			break;
+		case 'd':
+			software = strdup(optarg);
+			if (find_sw_load_params(optarg) != 0)
+				exit(0);
+			break;
+		case 'f':
+			firmware_analysis = optarg;
+			break;
+		case 'w':
+			dump_files = 1;
+			break;
+		case 'c':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		}
+	};
+
+	if (firmware_analysis)
+		analyze_firmware(firmware_analysis);
+
+	if (optind >= argc) {
+		/* only warn if we have not done anything else */
+		if (!firmware_analysis)
+			fprintf(stderr, "you have to specify the IP address of the BTS. Use --help for more information\n");
+		exit(2);
+	}
+
+	gsmnet = gsm_network_init(1, 1, NULL);
+	if (!gsmnet)
+		exit(1);
+
+	bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_NANOBTS, HARDCODED_TSC,
+				HARDCODED_BSIC);
+	/* ip.access supports up to 4 chained TRX */
+	gsm_bts_trx_alloc(bts);
+	gsm_bts_trx_alloc(bts);
+	gsm_bts_trx_alloc(bts);
+	bts->oml_tei = stream_id;
+	
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+	register_signal_handler(SS_IPAC_NWL, nwl_sig_cb, NULL);
+
+	ipac_nwl_init();
+
+	printf("Trying to connect to ip.access BTS ...\n");
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(argv[optind], &sin.sin_addr);
+	rc = ia_config_connect(bts, &sin);
+	if (rc < 0) {
+		perror("Error connecting to the BTS");
+		exit(1);
+	}
+	
+	bts->oml_link->ts->sign.delay = 10;
+	bts->c0->rsl_link->ts->sign.delay = 10;
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
diff --git a/src/ipaccess/ipaccess-find.c b/src/ipaccess/ipaccess-find.c
new file mode 100644
index 0000000..bea4b77
--- /dev/null
+++ b/src/ipaccess/ipaccess-find.c
@@ -0,0 +1,227 @@
+/* ip.access nanoBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+#include <osmocore/select.h>
+#include <osmocore/timer.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location 1",
+	[IPAC_IDTAG_LOCATION2]	= "Location 2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software Version",
+	[IPAC_IDTAG_IPADDR]	= "IP Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC Address",
+	[IPAC_IDTAG_UNIT]	= "Unit ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+static int udp_sock(const char *ifname)
+{
+	int fd, rc, bc = 1;
+	struct sockaddr_in sa;
+
+	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (fd < 0)
+		return fd;
+
+	if (ifname) {
+		rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
+				strlen(ifname));
+		if (rc < 0)
+			goto err;
+	}
+
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	sa.sin_addr.s_addr = INADDR_ANY;
+
+	rc = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+	if (rc < 0)
+		goto err;
+
+	rc = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bc, sizeof(bc));
+	if (rc < 0)
+		goto err;
+
+#if 0
+	/* we cannot bind, since the response packets don't come from
+	 * the broadcast address */
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	inet_aton("255.255.255.255", &sa.sin_addr);
+
+	rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
+	if (rc < 0)
+		goto err;
+#endif
+	return fd;
+
+err:
+	close(fd);
+	return rc;
+}
+
+const unsigned char find_pkt[] = { 0x00, 0x0b+8, IPAC_PROTO_IPACCESS, 0x00,
+				IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_IPADDR,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+
+static int bcast_find(int fd)
+{
+	struct sockaddr_in sa;
+
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(3006);
+	inet_aton("255.255.255.255", &sa.sin_addr);
+
+	return sendto(fd, find_pkt, sizeof(find_pkt), 0, (struct sockaddr *) &sa, sizeof(sa));
+}
+
+static int parse_response(unsigned char *buf, int len)
+{
+	u_int8_t t_len;
+	u_int8_t t_tag;
+	u_int8_t *cur = buf;
+
+	while (cur < buf + len) {
+		t_len = *cur++;
+		t_tag = *cur++;
+		
+		printf("%s='%s'  ", ipac_idtag_name(t_tag), cur);
+
+		cur += t_len;
+	}
+	printf("\n");
+	return 0;
+}
+
+static int read_response(int fd)
+{
+	unsigned char buf[255];
+	struct sockaddr_in sa;
+	int len;
+	socklen_t sa_len = sizeof(sa);
+
+	len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&sa, &sa_len);
+	if (len < 0)
+		return len;
+
+	/* 2 bytes length, 1 byte protocol (0xfe) */
+	if (buf[2] != 0xfe)
+		return 0;
+
+	if (buf[4] != IPAC_MSGT_ID_RESP)
+		return 0;
+
+	return parse_response(buf+6, len-6);
+}
+
+static int bfd_cb(struct bsc_fd *bfd, unsigned int flags)
+{
+	if (flags & BSC_FD_READ)
+		return read_response(bfd->fd);
+	if (flags & BSC_FD_WRITE) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return bcast_find(bfd->fd);
+	}
+	return 0;
+}
+
+static struct timer_list timer;
+
+static void timer_cb(void *_data)
+{
+	struct bsc_fd *bfd = _data;
+
+	bfd->when |= BSC_FD_WRITE;
+
+	bsc_schedule_timer(&timer, 5, 0);
+}
+
+int main(int argc, char **argv)
+{
+	struct bsc_fd bfd;
+	char *ifname;
+	int rc;
+
+	printf("ipaccess-find (C) 2009 by Harald Welte\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	if (argc < 2) {
+		fprintf(stdout, "you might need to specify the outgoing\n"
+			" network interface, e.g. ``%s eth0''\n", argv[0]);
+	}
+
+	ifname = argv[1];
+	bfd.cb = bfd_cb;
+	bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd.fd = udp_sock(ifname);
+	if (bfd.fd < 0) {
+		perror("Cannot create local socket for broadcast udp");
+		exit(1);
+	}
+
+	bsc_register_fd(&bfd);
+
+	timer.cb = timer_cb;
+	timer.data = &bfd;
+
+	bsc_schedule_timer(&timer, 5, 0);
+
+	printf("Trying to find ip.access BTS by broadcast UDP...\n");
+
+	while (1) {
+		rc = bsc_select_main(0);
+		if (rc < 0)
+			exit(3);
+	}
+
+	exit(0);
+}
+
diff --git a/src/ipaccess/ipaccess-firmware.c b/src/ipaccess/ipaccess-firmware.c
new file mode 100644
index 0000000..7fdd0f8
--- /dev/null
+++ b/src/ipaccess/ipaccess-firmware.c
@@ -0,0 +1,135 @@
+/* Routines for parsing an ipacces SDP firmware file */
+
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PART_LENGTH 138
+
+static_assert(sizeof(struct sdp_header_entry) == 138, right_entry);
+static_assert(sizeof(struct sdp_firmware) == 158, _right_header_length);
+
+/* more magic, the second "int" in the header */
+static char more_magic[] = { 0x10, 0x02 };
+
+int ipaccess_analyze_file(int fd, const unsigned int st_size, const unsigned int base_offset, struct llist_head *list)
+{
+	struct sdp_firmware *firmware_header = 0;
+	struct sdp_header *header;
+	char buf[4096];
+	int rc, i;
+	u_int16_t table_size;
+	u_int16_t table_offset;
+	off_t table_start;
+
+
+	rc = read(fd, buf, sizeof(*firmware_header));
+	if (rc < 0) {
+		perror("Can not read header start.");
+		return -1;
+	}
+
+	firmware_header = (struct sdp_firmware *) &buf[0];
+	if (strncmp(firmware_header->magic, " SDP", 4) != 0) {
+		fprintf(stderr, "Wrong magic.\n");
+		return -1;
+	}
+
+	if (memcmp(firmware_header->more_magic, more_magic, 2) != 0) {
+		fprintf(stderr, "Wrong more magic. Got: 0x%x %x %x %x\n",
+			firmware_header->more_magic[0] & 0xff, firmware_header->more_magic[1] & 0xff,
+			firmware_header->more_magic[2] & 0xff, firmware_header->more_magic[3] & 0xff);
+		return -1;
+	}
+
+
+	if (ntohl(firmware_header->file_length) != st_size) {
+		fprintf(stderr, "The filesize and the header do not match.\n");
+		return -1;
+	}
+
+	/* add the firmware */
+	header = talloc_zero(list, struct sdp_header);
+	header->firmware_info = *firmware_header;
+	INIT_LLIST_HEAD(&header->header_list);
+	llist_add(&header->entry, list);
+
+	table_offset = ntohs(firmware_header->table_offset);
+	table_start = lseek(fd, table_offset, SEEK_CUR);
+	if (table_start == -1) {
+		fprintf(stderr, "Failed to seek to the rel position: 0x%x\n", table_offset);
+		return -1;
+	}
+
+	if (read(fd, &table_size, sizeof(table_size)) != sizeof(table_size)) {
+		fprintf(stderr, "The table size could not be read.\n");
+		return -1;
+	}
+
+	table_size = ntohs(table_size);
+
+	if (table_size % PART_LENGTH != 0) {
+		fprintf(stderr, "The part length seems to be wrong: 0x%x\n", table_size);
+		return -1;
+	}
+
+	/* look into each firmware now */
+	for (i = 0; i < table_size / PART_LENGTH; ++i) {
+		struct sdp_header_entry entry;
+		struct sdp_header_item *header_entry;
+		unsigned int offset = table_start + 2;
+		offset += i * 138;
+
+		if (lseek(fd, offset, SEEK_SET) != offset) {
+			fprintf(stderr, "Can not seek to the offset: %u.\n", offset);
+			return -1;
+		}
+
+		rc = read(fd, &entry, sizeof(entry));
+		if (rc != sizeof(entry)) {
+			fprintf(stderr, "Can not read the header entry.\n");
+			return -1;
+		}
+
+		header_entry = talloc_zero(header,  struct sdp_header_item);
+		header_entry->header_entry = entry;
+		header_entry->absolute_offset = base_offset;
+		llist_add(&header_entry->entry, &header->header_list);
+
+		/* now we need to find the SDP file... */
+		offset = ntohl(entry.start) + 4 + base_offset;
+		if (lseek(fd, offset, SEEK_SET) != offset) {
+			perror("can't seek to sdp");
+			return -1;
+		}
+
+
+		ipaccess_analyze_file(fd, ntohl(entry.length), offset, list);
+	}
+
+	return 0;
+}
+
diff --git a/src/ipaccess/ipaccess-proxy.c b/src/ipaccess/ipaccess-proxy.c
new file mode 100644
index 0000000..2dc1b2f
--- /dev/null
+++ b/src/ipaccess/ipaccess-proxy.c
@@ -0,0 +1,1373 @@
+/* OpenBSC Abis/IP proxy ip.access nanoBTS */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by On-Waves
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+
+static struct log_target *stderr_target;
+
+/* one instance of an ip.access protocol proxy */
+struct ipa_proxy {
+	/* socket where we listen for incoming OML from BTS */
+	struct bsc_fd oml_listen_fd;
+	/* socket where we listen for incoming RSL from BTS */
+	struct bsc_fd rsl_listen_fd;
+	/* list of BTS's (struct ipa_bts_conn */
+	struct llist_head bts_list;
+	/* the BSC reconnect timer */
+	struct timer_list reconn_timer;
+	/* global GPRS NS data */
+	struct in_addr gprs_addr;
+	struct in_addr listen_addr;
+};
+
+/* global pointer to the proxy structure */
+static struct ipa_proxy *ipp;
+
+struct ipa_proxy_conn {
+	struct bsc_fd fd;
+	struct llist_head tx_queue;
+	struct ipa_bts_conn *bts_conn;
+};
+#define MAX_TRX 4
+
+/* represents a particular BTS in our proxy */
+struct ipa_bts_conn {
+	/* list of BTS's (ipa_proxy->bts_list) */
+	struct llist_head list;
+	/* back pointer to the proxy which we belong to */
+	struct ipa_proxy *ipp;
+	/* the unit ID as determined by CCM */
+	struct {
+		u_int16_t site_id;
+		u_int16_t bts_id;
+	} unit_id;
+
+	/* incoming connections from BTS */
+	struct ipa_proxy_conn *oml_conn;
+	struct ipa_proxy_conn *rsl_conn[MAX_TRX];
+
+	/* outgoing connections to BSC */
+	struct ipa_proxy_conn *bsc_oml_conn;
+	struct ipa_proxy_conn *bsc_rsl_conn[MAX_TRX];
+
+	/* UDP sockets for BTS and BSC injection */
+	struct bsc_fd udp_bts_fd;
+	struct bsc_fd udp_bsc_fd;
+
+	/* NS data */
+	struct in_addr bts_addr;
+	struct bsc_fd gprs_ns_fd;
+	int gprs_local_port;
+	uint16_t gprs_orig_port;
+	uint32_t gprs_orig_ip;
+
+	char *id_tags[0xff];
+	u_int8_t *id_resp;
+	unsigned int id_resp_len;
+};
+
+enum ipp_fd_type {
+	OML_FROM_BTS = 1,
+	RSL_FROM_BTS = 2,
+	OML_TO_BSC = 3,
+	RSL_TO_BSC = 4,
+	UDP_TO_BTS = 5,
+	UDP_TO_BSC = 6,
+};
+
+/* some of the code against we link from OpenBSC needs this */
+void *tall_bsc_ctx;
+
+static char *listen_ipaddr;
+static char *bsc_ipaddr;
+static char *gprs_ns_ipaddr;
+
+static int make_gprs_sock(struct bsc_fd *bfd, int (*cb)(struct bsc_fd*,unsigned int), void *);
+static int gprs_ns_cb(struct bsc_fd *bfd, unsigned int what);
+
+#define PROXY_ALLOC_SIZE	1200
+
+static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG };
+static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK };
+static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial_Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit_Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location_1",
+	[IPAC_IDTAG_LOCATION2]	= "Location_2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment_Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software_Version",
+	[IPAC_IDTAG_IPADDR]	= "IP_Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC_Address",
+	[IPAC_IDTAG_UNIT]	= "Unit_ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+static int ipac_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+	u_int8_t t_len;
+	u_int8_t t_tag;
+	u_int8_t *cur = buf;
+
+	while (cur < buf + len) {
+		t_len = *cur++;
+		t_tag = *cur++;
+
+		DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur);
+
+		dec->lv[t_tag].len = t_len;
+		dec->lv[t_tag].val = cur;
+
+		cur += t_len;
+	}
+	return 0;
+}
+
+static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id,
+			u_int16_t *trx_id)
+{
+	unsigned long ul;
+	char *endptr;
+	const char *nptr;
+
+	nptr = str;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (site_id)
+		*site_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (bts_id)
+		*bts_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (trx_id)
+		*trx_id = ul & 0xffff;
+
+	return 0;
+}
+
+static struct ipa_bts_conn *find_bts_by_unitid(struct ipa_proxy *ipp,
+						u_int16_t site_id,
+						u_int16_t bts_id)
+{
+	struct ipa_bts_conn *ipbc;
+
+	llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+		if (ipbc->unit_id.site_id == site_id &&
+		    ipbc->unit_id.bts_id == bts_id)
+			return ipbc;
+	}
+
+	return NULL;
+}
+
+struct ipa_proxy_conn *alloc_conn(void)
+{
+	struct ipa_proxy_conn *ipc;
+
+	ipc = talloc_zero(tall_bsc_ctx, struct ipa_proxy_conn);
+	if (!ipc)
+		return NULL;
+
+	INIT_LLIST_HEAD(&ipc->tx_queue);
+
+	return ipc;
+}
+
+static int store_idtags(struct ipa_bts_conn *ipbc, struct tlv_parsed *tlvp)
+{
+	unsigned int i, len;
+
+	for (i = 0; i <= 0xff; i++) {
+		if (!TLVP_PRESENT(tlvp, i))
+			continue;
+
+		len = TLVP_LEN(tlvp, i);
+#if 0
+		if (!ipbc->id_tags[i])
+			ipbc->id_tags[i] = talloc_size(tall_bsc_ctx, len);
+		else
+#endif
+			ipbc->id_tags[i] = talloc_realloc_size(ipbc,
+							  ipbc->id_tags[i], len);
+		if (!ipbc->id_tags[i])
+			return -ENOMEM;
+
+		memset(ipbc->id_tags[i], 0, len);
+		//memcpy(ipbc->id_tags[i], TLVP_VAL(tlvp, i), len);
+	}
+	return 0;
+}
+
+
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data);
+
+#define logp_ipbc_uid(ss, lvl, ipbc, trx_id) _logp_ipbc_uid(ss, lvl, __FILE__, __LINE__, ipbc, trx_id)
+
+static void _logp_ipbc_uid(unsigned int ss, unsigned int lvl, char *file, int line,
+			   struct ipa_bts_conn *ipbc, u_int8_t trx_id)
+{
+	if (ipbc)
+		logp2(ss, lvl, file, line, 0, "(%u/%u/%u) ", ipbc->unit_id.site_id,
+		     ipbc->unit_id.bts_id, trx_id);
+	else
+		logp2(ss, lvl, file, line, 0, "unknown ");
+}
+
+/* UDP socket handling */
+
+static int make_sock(struct bsc_fd *bfd, u_int16_t port, int proto, int priv_nr,
+		     int (*cb)(struct bsc_fd *fd, unsigned int what),
+		     void *data)
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_DGRAM, proto);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	bfd->data = data;
+	bfd->priv_nr = priv_nr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	addr.sin_addr.s_addr = INADDR_ANY;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not bind socket: %s\n",
+			strerror(errno));
+		return -EIO;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register UDP fd");
+		return ret;
+	}
+	return 0;
+}
+
+static int handle_udp_read(struct bsc_fd *bfd)
+{
+	struct ipa_bts_conn *ipbc = bfd->data;
+	struct ipa_proxy_conn *other_conn = NULL;
+	struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP UDP");
+	struct ipaccess_head *hh;
+	int ret;
+
+	/* with UDP sockets, we cannot read partial packets but have to read
+	 * all of it in one go */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, msg->data_len, 0);
+	if (ret < 0) {
+		if (errno != EAGAIN)
+			LOGP(DINP, LOGL_ERROR, "recv error  %s\n", strerror(errno));
+		msgb_free(msg);
+		return ret;
+	}
+	if (ret == 0) {
+		DEBUGP(DINP, "UDP peer disappeared, dead socket\n");
+		bsc_unregister_fd(bfd);
+		close(bfd->fd);
+		bfd->fd = -1;
+		msgb_free(msg);
+		return -EIO;
+	}
+	if (ret < sizeof(*hh)) {
+		DEBUGP(DINP, "could not even read header!?!\n");
+		msgb_free(msg);
+		return -EIO;
+	}
+	msgb_put(msg, ret);
+	msg->l2h = msg->data + sizeof(*hh);
+	DEBUGP(DMI, "UDP RX: %s\n", hexdump(msg->data, msg->len));
+
+	if (hh->len != msg->len - sizeof(*hh)) {
+		DEBUGP(DINP, "length (%u/%u) disagrees with header(%u)\n",
+			msg->len, msg->len - 3, hh->len);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	switch (bfd->priv_nr & 0xff) {
+	case UDP_TO_BTS:
+		/* injection towards BTS */
+		switch (hh->proto) {
+		case IPAC_PROTO_RSL:
+			/* FIXME: what to do about TRX > 0 */
+			other_conn = ipbc->rsl_conn[0];
+			break;
+		default:
+			DEBUGP(DINP, "Unknown protocol 0x%02x, sending to "
+				"OML FD\n", hh->proto);
+			/* fall through */
+		case IPAC_PROTO_IPACCESS:
+		case IPAC_PROTO_OML:
+			other_conn = ipbc->oml_conn;
+			break;
+		}
+		break;
+	case UDP_TO_BSC:
+		/* injection towards BSC */
+		switch (hh->proto) {
+		case IPAC_PROTO_RSL:
+			/* FIXME: what to do about TRX > 0 */
+			other_conn = ipbc->bsc_rsl_conn[0];
+			break;
+		default:
+			DEBUGP(DINP, "Unknown protocol 0x%02x, sending to "
+				"OML FD\n", hh->proto);
+		case IPAC_PROTO_IPACCESS:
+		case IPAC_PROTO_OML:
+			other_conn = ipbc->bsc_oml_conn;
+			break;
+		}
+		break;
+	default:
+		DEBUGP(DINP, "Unknown filedescriptor priv_nr=%04x\n", bfd->priv_nr);
+		break;
+	}
+
+	if (other_conn) {
+		/* enqueue the message for TX on the respective FD */
+		msgb_enqueue(&other_conn->tx_queue, msg);
+		other_conn->fd.when |= BSC_FD_WRITE;
+	} else
+		msgb_free(msg);
+
+	return 0;
+}
+
+static int handle_udp_write(struct bsc_fd *bfd)
+{
+	/* not implemented yet */
+	bfd->when &= ~BSC_FD_WRITE;
+
+	return -EIO;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int udp_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_udp_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_udp_write(bfd);
+
+	return rc;
+}
+
+
+static int ipbc_alloc_connect(struct ipa_proxy_conn *ipc, struct bsc_fd *bfd,
+			      u_int16_t site_id, u_int16_t bts_id,
+			      u_int16_t trx_id, struct tlv_parsed *tlvp,
+			      struct msgb *msg)
+{
+	struct ipa_bts_conn *ipbc;
+	u_int16_t udp_port;
+	int ret = 0;
+	struct sockaddr_in sin;
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+	DEBUGP(DINP, "(%u/%u/%u) New BTS connection: ",
+		site_id, bts_id, trx_id);
+
+	/* OML needs to be established before RSL */
+	if ((bfd->priv_nr & 0xff) != OML_FROM_BTS) {
+		DEBUGPC(DINP, "Not a OML connection ?!?\n");
+		return -EIO;
+	}
+
+	/* allocate new BTS connection data structure */
+	ipbc = talloc_zero(tall_bsc_ctx, struct ipa_bts_conn);
+	if (!ipbc) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	DEBUGPC(DINP, "Created BTS Conn data structure\n");
+	ipbc->ipp = ipp;
+	ipbc->unit_id.site_id = site_id;
+	ipbc->unit_id.bts_id = bts_id;
+	ipbc->oml_conn = ipc;
+	ipc->bts_conn = ipbc;
+
+	/* store the content of the ID TAGS for later reference */
+	store_idtags(ipbc, tlvp);
+	ipbc->id_resp_len = msg->len;
+	ipbc->id_resp = talloc_size(tall_bsc_ctx, ipbc->id_resp_len);
+	memcpy(ipbc->id_resp, msg->data, ipbc->id_resp_len);
+
+	/* Create OML TCP connection towards BSC */
+	sin.sin_port = htons(IPA_TCP_PORT_OML);
+	ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+	if (!ipbc->bsc_oml_conn) {
+		ret = -EIO;
+		goto err_bsc_conn;
+	}
+
+	DEBUGP(DINP, "(%u/%u/%u) OML Connected to BSC\n",
+		site_id, bts_id, trx_id);
+
+	/* Create UDP socket for BTS packet injection */
+	udp_port = 10000 + (site_id % 1000)*100 + (bts_id % 100);
+	ret = make_sock(&ipbc->udp_bts_fd, udp_port, IPPROTO_UDP,
+			UDP_TO_BTS, udp_fd_cb, ipbc);
+	if (ret < 0)
+		goto err_udp_bts;
+	DEBUGP(DINP, "(%u/%u/%u) Created UDP socket for injection "
+		"towards BTS at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+	/* Create UDP socket for BSC packet injection */
+	udp_port = 20000 + (site_id % 1000)*100 + (bts_id % 100);
+	ret = make_sock(&ipbc->udp_bsc_fd, udp_port, IPPROTO_UDP,
+			UDP_TO_BSC, udp_fd_cb, ipbc);
+	if (ret < 0)
+		goto err_udp_bsc;
+	DEBUGP(DINP, "(%u/%u/%u) Created UDP socket for injection "
+		"towards BSC at port %u\n", site_id, bts_id, trx_id, udp_port);
+
+
+	/* GPRS NS related code */
+	if (gprs_ns_ipaddr) {
+		struct sockaddr_in sock;
+		socklen_t len = sizeof(sock);
+		ret = make_gprs_sock(&ipbc->gprs_ns_fd, gprs_ns_cb, ipbc);
+		if (ret < 0) {
+			LOGP(DINP, LOGL_ERROR, "Creating the GPRS socket failed.\n");
+			goto err_udp_bsc;
+		}
+
+		ret = getsockname(ipbc->gprs_ns_fd.fd, (struct sockaddr* ) &sock, &len);
+		ipbc->gprs_local_port = ntohs(sock.sin_port);
+		LOGP(DINP, LOGL_NOTICE,
+			"Created GPRS NS Socket. Listening on: %s:%d\n",
+			inet_ntoa(sock.sin_addr), ipbc->gprs_local_port);
+
+		ret = getpeername(bfd->fd, (struct sockaddr* ) &sock, &len);
+		ipbc->bts_addr = sock.sin_addr;
+	}
+
+	llist_add(&ipbc->list, &ipp->bts_list);
+
+	return 0;
+
+err_udp_bsc:
+	bsc_unregister_fd(&ipbc->udp_bts_fd);
+err_udp_bts:
+	bsc_unregister_fd(&ipbc->bsc_oml_conn->fd);
+	close(ipbc->bsc_oml_conn->fd.fd);
+	talloc_free(ipbc->bsc_oml_conn);
+	ipbc->bsc_oml_conn = NULL;
+err_bsc_conn:
+	talloc_free(ipbc->id_resp);
+	talloc_free(ipbc);
+#if 0
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	talloc_free(bfd);
+#endif
+err_out:
+	return ret;
+}
+
+static int ipaccess_rcvmsg(struct ipa_proxy_conn *ipc, struct msgb *msg,
+			   struct bsc_fd *bfd)
+{
+	struct tlv_parsed tlvp;
+	u_int8_t msg_type = *(msg->l2h);
+	u_int16_t site_id, bts_id, trx_id;
+	struct ipa_bts_conn *ipbc;
+	int ret = 0;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = write(bfd->fd, pong, sizeof(pong));
+		if (ret < 0)
+			return ret;
+		if (ret < sizeof(pong)) {
+			DEBUGP(DINP, "short write\n");
+			return -EIO;
+		}
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DMI, "PONG!\n");
+		break;
+	case IPAC_MSGT_ID_RESP:
+		DEBUGP(DMI, "ID_RESP ");
+		/* parse tags, search for Unit ID */
+		ipac_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2,
+				 msgb_l2len(msg)-2);
+		DEBUGP(DMI, "\n");
+
+		if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT)) {
+			LOGP(DINP, LOGL_ERROR, "No Unit ID in ID RESPONSE !?!\n");
+			return -EIO;
+		}
+
+		/* lookup BTS, create sign_link, ... */
+		site_id = bts_id = trx_id = 0;
+		parse_unitid((char *)TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT),
+			     &site_id, &bts_id, &trx_id);
+		ipbc = find_bts_by_unitid(ipp, site_id, bts_id);
+		if (!ipbc) {
+			/* We have not found an ipbc (per-bts proxy instance)
+			 * for this BTS yet.  The first connection of a new BTS must
+			 * be a OML connection.  We allocate the associated data structures,
+			 * and try to connect to the remote end */
+
+			return ipbc_alloc_connect(ipc, bfd, site_id, bts_id,
+						  trx_id, &tlvp, msg);
+			/* if this fails, the caller will clean up bfd */
+		} else {
+			struct sockaddr_in sin;
+			memset(&sin, 0, sizeof(sin));
+			sin.sin_family = AF_INET;
+			inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+			DEBUGP(DINP, "Identified BTS %u/%u/%u\n",
+				site_id, bts_id, trx_id);
+
+			if ((bfd->priv_nr & 0xff) != RSL_FROM_BTS) {
+				LOGP(DINP, LOGL_ERROR, "Second OML connection from "
+				     "same BTS ?!?\n");
+				return 0;
+			}
+
+			if (trx_id > MAX_TRX) {
+				LOGP(DINP, LOGL_ERROR, "We don't support more "
+				     "than %u TRX\n", MAX_TRX);
+				return -EINVAL;
+			}
+
+			ipc->bts_conn = ipbc;
+			/* store TRX number in higher 8 bit of the bfd private number */
+			bfd->priv_nr |= trx_id << 8;
+			ipbc->rsl_conn[trx_id] = ipc;
+
+			/* Create RSL TCP connection towards BSC */
+			sin.sin_port = htons(IPA_TCP_PORT_RSL);
+			ipbc->bsc_rsl_conn[trx_id] =
+				connect_bsc(&sin, RSL_TO_BSC | (trx_id << 8), ipbc);
+			if (!ipbc->bsc_oml_conn)
+				return -EIO;
+			DEBUGP(DINP, "(%u/%u/%u) Connected RSL to BSC\n",
+				site_id, bts_id, trx_id);
+		}
+		break;
+	case IPAC_MSGT_ID_GET:
+		DEBUGP(DMI, "ID_GET\n");
+		if ((bfd->priv_nr & 0xff) != OML_TO_BSC &&
+		    (bfd->priv_nr & 0xff) != RSL_TO_BSC) {
+			DEBUGP(DINP, "IDentity REQuest from BTS ?!?\n");
+			return -EIO;
+		}
+		ipbc = ipc->bts_conn;
+		if (!ipbc) {
+			DEBUGP(DINP, "ID_GET from BSC before we have ID_RESP from BTS\n");
+			return -EIO;
+		}
+		ret = write(bfd->fd, ipbc->id_resp, ipbc->id_resp_len);
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DMI, "ID_ACK? -> ACK!\n");
+		ret = write(bfd->fd, id_ack, sizeof(id_ack));
+		break;
+	default:
+		LOGP(DMI, LOGL_ERROR, "Unhandled IPA type; %d\n", msg_type);
+		return 1;
+		break;
+	}
+	return 0;
+}
+
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error)
+{
+	struct msgb *msg = msgb_alloc(PROXY_ALLOC_SIZE, "Abis/IP");
+	struct ipaccess_head *hh;
+	int len, ret = 0;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	/* first read our 3-byte header */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, 3, 0);
+	if (ret < 0) {
+		if (errno != EAGAIN)
+			LOGP(DINP, LOGL_ERROR, "recv error: %s\n", strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msgb_put(msg, ret);
+
+	/* then read te length as specified in header */
+	msg->l2h = msg->data + sizeof(*hh);
+	len = ntohs(hh->len);
+	ret = recv(bfd->fd, msg->l2h, len, 0);
+	if (ret < len) {
+		LOGP(DINP, LOGL_ERROR, "short read!\n");
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+static struct ipa_proxy_conn *ipc_by_priv_nr(struct ipa_bts_conn *ipbc,
+					     unsigned int priv_nr)
+{
+	struct ipa_proxy_conn *bsc_conn;
+	unsigned int trx_id = priv_nr >> 8;
+
+	switch (priv_nr & 0xff) {
+	case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+		bsc_conn = ipbc->bsc_oml_conn;
+		break;
+	case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+		bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+		break;
+	case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+		bsc_conn = ipbc->oml_conn;
+		break;
+	case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+		bsc_conn = ipbc->rsl_conn[trx_id];
+		break;
+	default:
+		bsc_conn = NULL;
+		break;
+	}
+	return bsc_conn;
+}
+
+static void reconn_tmr_cb(void *data)
+{
+	struct ipa_proxy *ipp = data;
+	struct ipa_bts_conn *ipbc;
+	struct sockaddr_in sin;
+	int i;
+
+	DEBUGP(DINP, "Running reconnect timer\n");
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	inet_aton(bsc_ipaddr, &sin.sin_addr);
+
+	llist_for_each_entry(ipbc, &ipp->bts_list, list) {
+		/* if OML to BSC is dead, try to restore it */
+		if (ipbc->oml_conn && !ipbc->bsc_oml_conn) {
+			sin.sin_port = htons(IPA_TCP_PORT_OML);
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, 0);
+			LOGPC(DINP, LOGL_NOTICE, "OML Trying to reconnect\n");
+			ipbc->bsc_oml_conn = connect_bsc(&sin, OML_TO_BSC, ipbc);
+			if (!ipbc->bsc_oml_conn)
+				goto reschedule;
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, 0);
+			LOGPC(DINP, LOGL_NOTICE, "OML Reconnected\n");
+		}
+		/* if we (still) don't have a OML connection, skip RSL */
+		if (!ipbc->oml_conn || !ipbc->bsc_oml_conn)
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(ipbc->rsl_conn); i++) {
+			unsigned int priv_nr;
+			/* don't establish RSL links which we don't have */
+			if (!ipbc->rsl_conn[i])
+				continue;
+			if (ipbc->bsc_rsl_conn[i])
+				continue;
+			priv_nr = ipbc->rsl_conn[i]->fd.priv_nr;
+			priv_nr &= ~0xff;
+			priv_nr |= RSL_TO_BSC;
+			sin.sin_port = htons(IPA_TCP_PORT_RSL);
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+			LOGPC(DINP, LOGL_NOTICE, "RSL Trying to reconnect\n");
+			ipbc->bsc_rsl_conn[i] = connect_bsc(&sin, priv_nr, ipbc);
+			if (!ipbc->bsc_rsl_conn)
+				goto reschedule;
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, priv_nr >> 8);
+			LOGPC(DINP, LOGL_NOTICE, "RSL Reconnected\n");
+		}
+	}
+	return;
+
+reschedule:
+	bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+}
+
+static void handle_dead_socket(struct bsc_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;		/* local conn */
+	struct ipa_proxy_conn *bsc_conn;		/* remote conn */
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	unsigned int trx_id = bfd->priv_nr >> 8;
+	struct msgb *msg, *msg2;
+
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* FIXME: clear tx_queue, remove all references, etc. */
+	llist_for_each_entry_safe(msg, msg2, &ipc->tx_queue, list)
+		msgb_free(msg);
+
+	switch (bfd->priv_nr & 0xff) {
+	case OML_FROM_BTS: /* incoming OML data from BTS, forward to BSC OML */
+		ipbc->oml_conn = NULL;
+		bsc_conn = ipbc->bsc_oml_conn;
+		/* close the connection to the BSC */
+		bsc_unregister_fd(&bsc_conn->fd);
+		close(bsc_conn->fd.fd);
+		llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+			msgb_free(msg);
+		talloc_free(bsc_conn);
+		ipbc->bsc_oml_conn = NULL;
+		/* FIXME: do we need to delete the entire ipbc ? */
+		break;
+	case RSL_FROM_BTS: /* incoming RSL data from BTS, forward to BSC RSL */
+		ipbc->rsl_conn[trx_id] = NULL;
+		bsc_conn = ipbc->bsc_rsl_conn[trx_id];
+		/* close the connection to the BSC */
+		bsc_unregister_fd(&bsc_conn->fd);
+		close(bsc_conn->fd.fd);
+		llist_for_each_entry_safe(msg, msg2, &bsc_conn->tx_queue, list)
+			msgb_free(msg);
+		talloc_free(bsc_conn);
+		ipbc->bsc_rsl_conn[trx_id] = NULL;
+		break;
+	case OML_TO_BSC: /* incoming OML data from BSC, forward to BTS OML */
+		ipbc->bsc_oml_conn = NULL;
+		bsc_conn = ipbc->oml_conn;
+		/* start reconnect timer */
+		bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+		break;
+	case RSL_TO_BSC: /* incoming RSL data from BSC, forward to BTS RSL */
+		ipbc->bsc_rsl_conn[trx_id] = NULL;
+		bsc_conn = ipbc->rsl_conn[trx_id];
+		/* start reconnect timer */
+		bsc_schedule_timer(&ipp->reconn_timer, 5, 0);
+		break;
+	default:
+		bsc_conn = NULL;
+		break;
+	}
+
+	talloc_free(ipc);
+}
+
+static void patch_gprs_msg(struct ipa_bts_conn *ipbc, int priv_nr, struct msgb *msg)
+{
+	uint8_t *nsvci;
+
+	if ((priv_nr & 0xff) != OML_FROM_BTS && (priv_nr & 0xff) != OML_TO_BSC)
+		return;
+
+	if (msgb_l2len(msg) != 39)
+		return;
+
+	/*
+	 * Check if this is a IPA Set Attribute or IPA Set Attribute ACK
+	 * and if the FOM Class is GPRS NSVC0 and then we will patch it.
+	 *
+	 * The patch assumes the message looks like the one from the trace
+	 * but we only match messages with a specific size anyway... So
+	 * this hack should work just fine.
+	 */
+
+	if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 &&
+	    msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 &&
+	    msg->l2h[18] == 0xf5 && msg->l2h[19] == 0xf2) {
+		nsvci = &msg->l2h[23];
+		ipbc->gprs_orig_port =  *(u_int16_t *)(nsvci+8);
+		ipbc->gprs_orig_ip = *(u_int32_t *)(nsvci+10);
+		*(u_int16_t *)(nsvci+8) = htons(ipbc->gprs_local_port);
+		*(u_int32_t *)(nsvci+10) = ipbc->ipp->listen_addr.s_addr;
+	} else if (msg->l2h[0] == 0x10 && msg->l2h[1] == 0x80 &&
+	    msg->l2h[2] == 0x00 && msg->l2h[3] == 0x15 &&
+	    msg->l2h[18] == 0xf6 && msg->l2h[19] == 0xf2) {
+		nsvci = &msg->l2h[23];
+		*(u_int16_t *)(nsvci+8) = ipbc->gprs_orig_port;
+		*(u_int32_t *)(nsvci+10) = ipbc->gprs_orig_ip;
+	}
+}
+
+static int handle_tcp_read(struct bsc_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	struct ipa_proxy_conn *bsc_conn;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+	int ret = 0;
+	char *btsbsc;
+
+	if ((bfd->priv_nr & 0xff) <= 2)
+		btsbsc = "BTS";
+	else
+		btsbsc = "BSC";
+
+	msg = ipaccess_read_msg(bfd, &ret);
+	if (!msg) {
+		if (ret == 0) {
+			logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+			LOGPC(DINP, LOGL_NOTICE, "%s disappeared, "
+			     "dead socket\n", btsbsc);
+			handle_dead_socket(bfd);
+		}
+		return ret;
+	}
+
+	msgb_put(msg, ret);
+	logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+	DEBUGPC(DMI, "RX<-%s: %s\n", btsbsc, hexdump(msg->data, msg->len));
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ret = ipaccess_rcvmsg(ipc, msg, bfd);
+		if (ret < 0) {
+			bsc_unregister_fd(bfd);
+			close(bfd->fd);
+			bfd->fd = -1;
+			talloc_free(bfd);
+			msgb_free(msg);
+			return ret;
+		} else if (ret == 0) {
+			/* we do not forward parts of the CCM protocol
+			 * through the proxy but rather terminate it ourselves. */
+			msgb_free(msg);
+			return ret;
+		}
+	}
+
+	if (!ipbc) {
+		LOGP(DINP, LOGL_ERROR,
+		     "received %s packet but no ipc->bts_conn?!?\n", btsbsc);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	bsc_conn = ipc_by_priv_nr(ipbc, bfd->priv_nr);
+	if (bsc_conn) {
+		if (gprs_ns_ipaddr)
+			patch_gprs_msg(ipbc, bfd->priv_nr, msg);
+		/* enqueue packet towards BSC */
+		msgb_enqueue(&bsc_conn->tx_queue, msg);
+		/* mark respective filedescriptor as 'we want to write' */
+		bsc_conn->fd.when |= BSC_FD_WRITE;
+	} else {
+		logp_ipbc_uid(DINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8);
+		LOGPC(DINP, LOGL_INFO, "Dropping packet from %s, "
+		     "since remote connection is dead\n", btsbsc);
+		msgb_free(msg);
+	}
+
+	return ret;
+}
+
+/* a TCP socket is ready to be written to */
+static int handle_tcp_write(struct bsc_fd *bfd)
+{
+	struct ipa_proxy_conn *ipc = bfd->data;
+	struct ipa_bts_conn *ipbc = ipc->bts_conn;
+	struct llist_head *lh;
+	struct msgb *msg;
+	char *btsbsc;
+	int ret;
+
+	if ((bfd->priv_nr & 0xff) <= 2)
+		btsbsc = "BTS";
+	else
+		btsbsc = "BSC";
+
+
+	/* get the next msg for this timeslot */
+	if (llist_empty(&ipc->tx_queue)) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+	lh = ipc->tx_queue.next;
+	llist_del(lh);
+	msg = llist_entry(lh, struct msgb, list);
+
+	logp_ipbc_uid(DMI, LOGL_DEBUG, ipbc, bfd->priv_nr >> 8);
+	DEBUGPC(DMI, "TX %04x: %s\n", bfd->priv_nr,
+		hexdump(msg->data, msg->len));
+
+	ret = send(bfd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	if (ret == 0) {
+		logp_ipbc_uid(DINP, LOGL_NOTICE, ipbc, bfd->priv_nr >> 8);
+		LOGP(DINP, LOGL_NOTICE, "%s disappeared, dead socket\n", btsbsc);
+		handle_dead_socket(bfd);
+	}
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ) {
+		rc = handle_tcp_read(bfd);
+		if (rc < 0)
+			return rc;
+	}
+	if (what & BSC_FD_WRITE)
+		rc = handle_tcp_write(bfd);
+
+	return rc;
+}
+
+/* callback of the listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	struct ipa_proxy_conn *ipc;
+	struct bsc_fd *bfd;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		perror("accept");
+		return ret;
+	}
+	DEBUGP(DINP, "accept()ed new %s link from %s\n",
+		(listen_bfd->priv_nr & 0xff) == OML_FROM_BTS ? "OML" : "RSL",
+		inet_ntoa(sa.sin_addr));
+
+	ipc = alloc_conn();
+	if (!ipc) {
+		close(ret);
+		return -ENOMEM;
+	}
+
+	bfd = &ipc->fd;
+	bfd->fd = ret;
+	bfd->data = ipc;
+	bfd->priv_nr = listen_bfd->priv_nr;
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(ipc);
+		return ret;
+	}
+
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = write(bfd->fd, id_req, sizeof(id_req));
+
+	return 0;
+}
+
+static void send_ns(int fd, const char *buf, int size, struct in_addr ip, int port)
+{
+	int ret;
+	struct sockaddr_in addr;
+	socklen_t len = sizeof(addr);
+	memset(&addr, 0, sizeof(addr));
+
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	addr.sin_addr = ip;
+
+	ret = sendto(fd, buf, size, 0, (struct sockaddr *) &addr, len);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to forward GPRS message.\n");
+	}
+}
+
+static int gprs_ns_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct ipa_bts_conn *bts;
+	char buf[4096];
+	int ret;
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+
+	/* 1. get the data... */
+	ret = recvfrom(bfd->fd, buf, sizeof(buf), 0, (struct sockaddr *) &sock, &len);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to recv GPRS NS msg: %s.\n", strerror(errno));
+		return -1;
+	}
+
+	bts = bfd->data;
+
+	/* 2. figure out where to send it to */
+	if (memcmp(&sock.sin_addr, &ipp->gprs_addr, sizeof(sock.sin_addr)) == 0) {
+		LOGP(DINP, LOGL_DEBUG, "GPRS NS msg from network.\n");
+		send_ns(bfd->fd, buf, ret, bts->bts_addr, 23000);
+	} else if (memcmp(&sock.sin_addr, &bts->bts_addr, sizeof(sock.sin_addr)) == 0) {
+		LOGP(DINP, LOGL_DEBUG, "GPRS NS msg from BTS.\n");
+		send_ns(bfd->fd, buf, ret, ipp->gprs_addr, 23000);
+	} else {
+		LOGP(DINP, LOGL_ERROR, "Unknown GPRS source: %s\n", inet_ntoa(sock.sin_addr));
+	}
+
+	return 0;
+}
+
+static int make_listen_sock(struct bsc_fd *bfd, u_int16_t port, int priv_nr,
+		     int (*cb)(struct bsc_fd *fd, unsigned int what))
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	bfd->priv_nr = priv_nr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	if (!listen_ipaddr)
+		addr.sin_addr.s_addr = INADDR_ANY;
+	else
+		inet_aton(listen_ipaddr, &addr.sin_addr);
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR,
+			"Could not bind listen socket for IP %s with error: %s.\n",
+			listen_ipaddr, strerror(errno));
+		return -EIO;
+	}
+
+	ret = listen(bfd->fd, 1);
+	if (ret < 0) {
+		perror("listen");
+		return ret;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		return ret;
+	}
+	return 0;
+}
+
+static int make_gprs_sock(struct bsc_fd *bfd, int (*cb)(struct bsc_fd*,unsigned int), void *data)
+{
+	struct sockaddr_in addr;
+	int ret;
+
+	bfd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	bfd->cb = cb;
+	bfd->data = data;
+	bfd->when = BSC_FD_READ;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = 0;
+	inet_aton(listen_ipaddr, &addr.sin_addr);
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR,
+			"Could not bind n socket for IP %s with error: %s.\n",
+			listen_ipaddr, strerror(errno));
+		return -EIO;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		return ret;
+	}
+	return 0;
+}
+
+/* Actively connect to a BSC.  */
+static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, void *data)
+{
+	struct ipa_proxy_conn *ipc;
+	struct bsc_fd *bfd;
+	int ret, on = 1;
+
+	ipc = alloc_conn();
+	if (!ipc)
+		return NULL;
+
+	ipc->bts_conn = data;
+
+	bfd = &ipc->fd;
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd->data = ipc;
+	bfd->priv_nr = priv_nr;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "Could not connect socket: %s\n",
+		     inet_ntoa(sa->sin_addr));
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	/* pre-fill tx_queue with identity request */
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		close(bfd->fd);
+		talloc_free(ipc);
+		return NULL;
+	}
+
+	return ipc;
+}
+
+static int ipaccess_proxy_setup(void)
+{
+	int ret;
+
+	ipp = talloc_zero(tall_bsc_ctx, struct ipa_proxy);
+	if (!ipp)
+		return -ENOMEM;
+	INIT_LLIST_HEAD(&ipp->bts_list);
+	ipp->reconn_timer.cb = reconn_tmr_cb;
+	ipp->reconn_timer.data = ipp;
+
+	/* Listen for OML connections */
+	ret = make_listen_sock(&ipp->oml_listen_fd, IPA_TCP_PORT_OML,
+				OML_FROM_BTS, listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	/* Listen for RSL connections */
+	ret = make_listen_sock(&ipp->rsl_listen_fd, IPA_TCP_PORT_RSL,
+				RSL_FROM_BTS, listen_fd_cb);
+
+	if (ret < 0)
+		return ret;
+
+	/* Connect the GPRS NS Socket */
+	if (gprs_ns_ipaddr) {
+		inet_aton(gprs_ns_ipaddr, &ipp->gprs_addr);
+		inet_aton(listen_ipaddr, &ipp->listen_addr);
+	}
+
+	return ret;
+}
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+static void print_help()
+{
+	printf(" ipaccess-proxy is a proxy BTS.\n");
+	printf(" -h --help. This help text.\n");
+	printf(" -l --listen IP. The ip to listen to.\n");
+	printf(" -b --bsc IP. The BSC IP address.\n");
+	printf(" -g --gprs IP. Take GPRS NS from that IP.\n");
+	printf("\n");
+	printf(" -s --disable-color. Disable the color inside the logging message.\n");
+	printf(" -e --log-level number. Set the global loglevel.\n");
+	printf(" -T --timestamp. Prefix every log message with a timestamp.\n");
+	printf(" -V --version. Print the version of OpenBSC.\n");
+}
+
+static void print_usage()
+{
+	printf("Usage: ipaccess-proxy\n");
+}
+
+static void handle_options(int argc, char** argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"log-level", 1, 0, 'e'},
+			{"listen", 1, 0, 'l'},
+			{"bsc", 1, 0, 'b'},
+			{"udp", 1, 0, 'u'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hsTe:l:b:g:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 'l':
+			listen_ipaddr = optarg;
+			break;
+		case 'b':
+			bsc_ipaddr = optarg;
+			break;
+		case 'g':
+			gprs_ns_ipaddr = optarg;
+			break;
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'e':
+			log_set_log_level(stderr_target, atoi(optarg));
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	listen_ipaddr = "192.168.100.11";
+	bsc_ipaddr = "192.168.100.239";
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "ipaccess-proxy");
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	log_parse_category_mask(stderr_target, "DINP:DMI");
+
+	handle_options(argc, argv);
+
+	rc = ipaccess_proxy_setup();
+	if (rc < 0)
+		exit(1);
+
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+
+	while (1) {
+		bsc_select_main(0);
+	}
+}
diff --git a/src/ipaccess/network_listen.c b/src/ipaccess/network_listen.c
new file mode 100644
index 0000000..aaf7c97
--- /dev/null
+++ b/src/ipaccess/network_listen.c
@@ -0,0 +1,251 @@
+/* ip.access nanoBTS network listen mode */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rxlev_stat.h>
+#include <osmocore/gsm48_ie.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+
+#define WHITELIST_MAX_SIZE ((NUM_ARFCNS*2)+2+1)
+
+int ipac_rxlevstat2whitelist(uint16_t *buf, const struct rxlev_stats *st, uint8_t min_rxlev,
+			     uint16_t max_num_arfcns)
+{
+	int i;
+	unsigned int num_arfcn = 0;
+
+	for (i = NUM_RXLEVS-1; i >= min_rxlev; i--) {
+		int16_t arfcn = -1;
+
+		while ((arfcn = rxlev_stat_get_next(st, i, arfcn)) >= 0) {
+			*buf++ = htons(arfcn);
+			num_arfcn++;
+
+		}
+
+		if (num_arfcn > max_num_arfcns)
+			break;
+	}
+
+	return num_arfcn;
+}
+
+enum ipac_test_state {
+	IPAC_TEST_S_IDLE,
+	IPAC_TEST_S_RQD,
+	IPAC_TEST_S_EXEC,
+	IPAC_TEST_S_PARTIAL,
+};
+
+int ipac_nwl_test_start(struct gsm_bts_trx *trx, uint8_t testnr,
+			const uint8_t *phys_conf, unsigned int phys_conf_len)
+{
+	struct msgb *msg;
+
+	if (trx->ipaccess.test_state != IPAC_TEST_S_IDLE) {
+		fprintf(stderr, "Cannot start test in state %u\n", trx->ipaccess.test_state);
+		return -EINVAL;
+	}
+
+	switch (testnr) {
+	case NM_IPACC_TESTNO_CHAN_USAGE:
+	case NM_IPACC_TESTNO_BCCH_CHAN_USAGE:
+		rxlev_stat_reset(&trx->ipaccess.rxlev_stat);
+		break;
+	}
+
+	msg = msgb_alloc_headroom(phys_conf_len+256, 128, "OML");
+
+	if (phys_conf && phys_conf_len) {
+		uint8_t *payload;
+		/* first put the phys conf header */
+		msgb_tv16_put(msg, NM_ATT_PHYS_CONF, phys_conf_len);
+		payload = msgb_put(msg, phys_conf_len);
+		memcpy(payload, phys_conf, phys_conf_len);
+	}
+
+	abis_nm_perform_test(trx->bts, NM_OC_RADIO_CARRIER, 0, trx->nr, 0xff,
+			     testnr, 1, msg);
+	trx->ipaccess.test_nr = testnr;
+
+	/* FIXME: start safety timer until when test is supposed to complete */
+
+	return 0;
+}
+
+static uint16_t last_arfcn;
+static struct gsm_sysinfo_freq nwl_si_freq[1024];
+#define FREQ_TYPE_NCELL_2	0x04 /* sub channel of SI 2 */
+#define FREQ_TYPE_NCELL_2bis	0x08 /* sub channel of SI 2bis */
+#define FREQ_TYPE_NCELL_2ter	0x10 /* sub channel of SI 2ter */
+
+struct ipacc_ferr_elem {
+	int16_t freq_err;
+	uint8_t freq_qual;
+	uint8_t arfcn;
+} __attribute__((packed));
+
+struct ipacc_cusage_elem {
+	uint16_t arfcn:10,
+		  rxlev:6;
+} __attribute__ ((packed));
+
+static int test_rep(void *_msg)
+{
+	struct msgb *msg = _msg;
+	struct abis_om_fom_hdr *foh = msgb_l3(msg);
+	uint16_t test_rep_len, ferr_list_len;
+	struct ipacc_ferr_elem *ife;
+	struct ipac_bcch_info binfo;
+	int i, rc;
+
+	DEBUGP(DNM, "TEST REPORT: ");
+
+	if (foh->data[0] != NM_ATT_TEST_NO ||
+	    foh->data[2] != NM_ATT_TEST_REPORT)
+		return -EINVAL;
+
+	DEBUGPC(DNM, "test_no=0x%02x ", foh->data[1]);
+	/* data[2] == NM_ATT_TEST_REPORT */
+	/* data[3..4]: test_rep_len */
+	test_rep_len = ntohs(*(uint16_t *) &foh->data[3]);
+	/* data[5]: ip.access test result */
+	DEBUGPC(DNM, "tst_res=%s\n", ipacc_testres_name(foh->data[5]));
+
+	/* data[6]: ip.access nested IE. 3 == freq_err_list */
+	switch (foh->data[6]) {
+	case NM_IPAC_EIE_FREQ_ERR_LIST:
+		/* data[7..8]: length of ferr_list */
+		ferr_list_len = ntohs(*(uint16_t *) &foh->data[7]);
+
+		/* data[9...]: frequency error list elements */
+		for (i = 0; i < ferr_list_len; i+= sizeof(*ife)) {
+			ife = (struct ipacc_ferr_elem *) (foh->data + 9 + i);
+			DEBUGP(DNM, "==> ARFCN %4u, Frequency Error %6hd\n",
+			ife->arfcn, ntohs(ife->freq_err));
+		}
+		break;
+	case NM_IPAC_EIE_CHAN_USE_LIST:
+		/* data[7..8]: length of ferr_list */
+		ferr_list_len = ntohs(*(uint16_t *) &foh->data[7]);
+
+		/* data[9...]: channel usage list elements */
+		for (i = 0; i < ferr_list_len; i+= 2) {
+			uint16_t *cu_ptr = (uint16_t *)(foh->data + 9 + i);
+			uint16_t cu = ntohs(*cu_ptr);
+			uint16_t arfcn = cu & 0x3ff;
+			uint8_t rxlev = cu >> 10;
+			DEBUGP(DNM, "==> ARFCN %4u, RxLev %2u\n", arfcn, rxlev);
+			rxlev_stat_input(&msg->trx->ipaccess.rxlev_stat,
+					 arfcn, rxlev);
+		}
+		break;
+	case NM_IPAC_EIE_BCCH_INFO_TYPE:
+		break;
+	case NM_IPAC_EIE_BCCH_INFO:
+		rc = ipac_parse_bcch_info(&binfo, foh->data+6);
+		if (rc < 0) {
+			DEBUGP(DNM, "BCCH Info parsing failed\n");
+			break;
+		}
+		DEBUGP(DNM, "==> ARFCN %u, RxLev %2u, RxQual %2u: %3d-%d, LAC %d CI %d BSIC %u\n",
+			binfo.arfcn, binfo.rx_lev, binfo.rx_qual,
+			binfo.cgi.mcc, binfo.cgi.mnc,
+			binfo.cgi.lac, binfo.cgi.ci, binfo.bsic);
+
+		if (binfo.arfcn != last_arfcn) {
+			/* report is on a new arfcn, need to clear channel list */
+			memset(nwl_si_freq, 0, sizeof(nwl_si_freq));
+			last_arfcn = binfo.arfcn;
+		}
+		if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2) {
+			DEBUGP(DNM, "BA SI2: %s\n", hexdump(binfo.ba_list_si2, sizeof(binfo.ba_list_si2)));
+			gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2, sizeof(binfo.ba_list_si2),
+						0x8c, FREQ_TYPE_NCELL_2);
+		}
+		if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+			DEBUGP(DNM, "BA SI2bis: %s\n", hexdump(binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis)));
+			gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2bis, sizeof(binfo.ba_list_si2bis),
+						0x8e, FREQ_TYPE_NCELL_2bis);
+		}
+		if (binfo.info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+			DEBUGP(DNM, "BA SI2ter: %s\n", hexdump(binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter)));
+			gsm48_decode_freq_list(nwl_si_freq, binfo.ba_list_si2ter, sizeof(binfo.ba_list_si2ter),
+						0x8e, FREQ_TYPE_NCELL_2ter);
+		}
+		for (i = 0; i < ARRAY_SIZE(nwl_si_freq); i++) {
+			if (nwl_si_freq[i].mask)
+				DEBUGP(DNM, "Neighbor Cell on ARFCN %u\n", i);
+		}
+		break;
+	default:
+		break;
+	}
+
+	switch (foh->data[5]) {
+	case NM_IPACC_TESTRES_SUCCESS:
+	case NM_IPACC_TESTRES_STOPPED:
+	case NM_IPACC_TESTRES_TIMEOUT:
+	case NM_IPACC_TESTRES_NO_CHANS:
+		msg->trx->ipaccess.test_state = IPAC_TEST_S_IDLE;
+		/* Send signal to notify higher layers of test completion */
+		DEBUGP(DNM, "dispatching S_IPAC_NWL_COMPLETE signal\n");
+		dispatch_signal(SS_IPAC_NWL, S_IPAC_NWL_COMPLETE, msg->trx);
+		break;
+	case NM_IPACC_TESTRES_PARTIAL:
+		msg->trx->ipaccess.test_state = IPAC_TEST_S_PARTIAL;
+		break;
+	}
+
+	return 0;
+}
+
+static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	switch (signal) {
+	case S_NM_TEST_REP:
+		return test_rep(signal_data);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+void ipac_nwl_init(void)
+{
+	register_signal_handler(SS_NM, nwl_sig_cb, NULL);
+}
diff --git a/src/libabis/Makefile.am b/src/libabis/Makefile.am
new file mode 100644
index 0000000..0df7b5a
--- /dev/null
+++ b/src/libabis/Makefile.am
@@ -0,0 +1,14 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libabis.a
+
+libabis_a_SOURCES = e1_input.c e1_input_vty.c \
+			input/misdn.c		\
+			input/ipaccess.c	\
+			input/hsl.c		\
+			input/dahdi.c		\
+			input/lapd.c
+
+EXTRA_DIST = input/lapd.h
diff --git a/src/libabis/Makefile.in b/src/libabis/Makefile.in
new file mode 100644
index 0000000..90d6287
--- /dev/null
+++ b/src/libabis/Makefile.in
@@ -0,0 +1,548 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libabis
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libabis_a_AR = $(AR) $(ARFLAGS)
+libabis_a_LIBADD =
+am_libabis_a_OBJECTS = e1_input.$(OBJEXT) e1_input_vty.$(OBJEXT) \
+	misdn.$(OBJEXT) ipaccess.$(OBJEXT) hsl.$(OBJEXT) \
+	dahdi.$(OBJEXT) lapd.$(OBJEXT)
+libabis_a_OBJECTS = $(am_libabis_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_$(V))
+am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY))
+am__v_lt_0 = --silent
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libabis_a_SOURCES)
+DIST_SOURCES = $(libabis_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libabis.a
+libabis_a_SOURCES = e1_input.c e1_input_vty.c \
+			input/misdn.c		\
+			input/ipaccess.c	\
+			input/hsl.c		\
+			input/dahdi.c		\
+			input/lapd.c
+
+EXTRA_DIST = input/lapd.h
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libabis/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libabis/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libabis.a: $(libabis_a_OBJECTS) $(libabis_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libabis.a
+	$(AM_V_AR)$(libabis_a_AR) libabis.a $(libabis_a_OBJECTS) $(libabis_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libabis.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dahdi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_input.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_input_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hsl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ipaccess.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lapd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misdn.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+misdn.o: input/misdn.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misdn.o -MD -MP -MF $(DEPDIR)/misdn.Tpo -c -o misdn.o `test -f 'input/misdn.c' || echo '$(srcdir)/'`input/misdn.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/misdn.Tpo $(DEPDIR)/misdn.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/misdn.c' object='misdn.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misdn.o `test -f 'input/misdn.c' || echo '$(srcdir)/'`input/misdn.c
+
+misdn.obj: input/misdn.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misdn.obj -MD -MP -MF $(DEPDIR)/misdn.Tpo -c -o misdn.obj `if test -f 'input/misdn.c'; then $(CYGPATH_W) 'input/misdn.c'; else $(CYGPATH_W) '$(srcdir)/input/misdn.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/misdn.Tpo $(DEPDIR)/misdn.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/misdn.c' object='misdn.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misdn.obj `if test -f 'input/misdn.c'; then $(CYGPATH_W) 'input/misdn.c'; else $(CYGPATH_W) '$(srcdir)/input/misdn.c'; fi`
+
+ipaccess.o: input/ipaccess.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ipaccess.o -MD -MP -MF $(DEPDIR)/ipaccess.Tpo -c -o ipaccess.o `test -f 'input/ipaccess.c' || echo '$(srcdir)/'`input/ipaccess.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ipaccess.Tpo $(DEPDIR)/ipaccess.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/ipaccess.c' object='ipaccess.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ipaccess.o `test -f 'input/ipaccess.c' || echo '$(srcdir)/'`input/ipaccess.c
+
+ipaccess.obj: input/ipaccess.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ipaccess.obj -MD -MP -MF $(DEPDIR)/ipaccess.Tpo -c -o ipaccess.obj `if test -f 'input/ipaccess.c'; then $(CYGPATH_W) 'input/ipaccess.c'; else $(CYGPATH_W) '$(srcdir)/input/ipaccess.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ipaccess.Tpo $(DEPDIR)/ipaccess.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/ipaccess.c' object='ipaccess.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ipaccess.obj `if test -f 'input/ipaccess.c'; then $(CYGPATH_W) 'input/ipaccess.c'; else $(CYGPATH_W) '$(srcdir)/input/ipaccess.c'; fi`
+
+hsl.o: input/hsl.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hsl.o -MD -MP -MF $(DEPDIR)/hsl.Tpo -c -o hsl.o `test -f 'input/hsl.c' || echo '$(srcdir)/'`input/hsl.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/hsl.Tpo $(DEPDIR)/hsl.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/hsl.c' object='hsl.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hsl.o `test -f 'input/hsl.c' || echo '$(srcdir)/'`input/hsl.c
+
+hsl.obj: input/hsl.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT hsl.obj -MD -MP -MF $(DEPDIR)/hsl.Tpo -c -o hsl.obj `if test -f 'input/hsl.c'; then $(CYGPATH_W) 'input/hsl.c'; else $(CYGPATH_W) '$(srcdir)/input/hsl.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/hsl.Tpo $(DEPDIR)/hsl.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/hsl.c' object='hsl.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hsl.obj `if test -f 'input/hsl.c'; then $(CYGPATH_W) 'input/hsl.c'; else $(CYGPATH_W) '$(srcdir)/input/hsl.c'; fi`
+
+dahdi.o: input/dahdi.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dahdi.o -MD -MP -MF $(DEPDIR)/dahdi.Tpo -c -o dahdi.o `test -f 'input/dahdi.c' || echo '$(srcdir)/'`input/dahdi.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/dahdi.Tpo $(DEPDIR)/dahdi.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/dahdi.c' object='dahdi.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dahdi.o `test -f 'input/dahdi.c' || echo '$(srcdir)/'`input/dahdi.c
+
+dahdi.obj: input/dahdi.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dahdi.obj -MD -MP -MF $(DEPDIR)/dahdi.Tpo -c -o dahdi.obj `if test -f 'input/dahdi.c'; then $(CYGPATH_W) 'input/dahdi.c'; else $(CYGPATH_W) '$(srcdir)/input/dahdi.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/dahdi.Tpo $(DEPDIR)/dahdi.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/dahdi.c' object='dahdi.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dahdi.obj `if test -f 'input/dahdi.c'; then $(CYGPATH_W) 'input/dahdi.c'; else $(CYGPATH_W) '$(srcdir)/input/dahdi.c'; fi`
+
+lapd.o: input/lapd.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lapd.o -MD -MP -MF $(DEPDIR)/lapd.Tpo -c -o lapd.o `test -f 'input/lapd.c' || echo '$(srcdir)/'`input/lapd.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/lapd.Tpo $(DEPDIR)/lapd.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/lapd.c' object='lapd.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lapd.o `test -f 'input/lapd.c' || echo '$(srcdir)/'`input/lapd.c
+
+lapd.obj: input/lapd.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lapd.obj -MD -MP -MF $(DEPDIR)/lapd.Tpo -c -o lapd.obj `if test -f 'input/lapd.c'; then $(CYGPATH_W) 'input/lapd.c'; else $(CYGPATH_W) '$(srcdir)/input/lapd.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/lapd.Tpo $(DEPDIR)/lapd.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='input/lapd.c' object='lapd.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lapd.obj `if test -f 'input/lapd.c'; then $(CYGPATH_W) 'input/lapd.c'; else $(CYGPATH_W) '$(srcdir)/input/lapd.c'; fi`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libabis/e1_input.c b/src/libabis/e1_input.c
new file mode 100644
index 0000000..3b6644e
--- /dev/null
+++ b/src/libabis/e1_input.c
@@ -0,0 +1,649 @@
+/* OpenBSC Abis interface to E1 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <mISDNif.h>
+
+//#define AF_COMPATIBILITY_FUNC
+//#include <compat_af_isdn.h>
+#ifndef AF_ISDN
+#define AF_ISDN 34
+#define PF_ISDN AF_ISDN
+#endif
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/misdn.h>
+
+#include "../../bscconfig.h"
+
+#define NUM_E1_TS	32
+
+/* list of all E1 drivers */
+LLIST_HEAD(e1inp_driver_list);
+
+/* list of all E1 lines */
+LLIST_HEAD(e1inp_line_list);
+
+static void *tall_sigl_ctx;
+
+/*
+ * pcap writing of the misdn load
+ * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+#define DLT_LINUX_LAPD		177
+#define PCAP_INPUT		0
+#define PCAP_OUTPUT		1
+
+struct pcap_hdr {
+	u_int32_t magic_number;
+	u_int16_t version_major;
+	u_int16_t version_minor;
+	int32_t  thiszone;
+	u_int32_t sigfigs;
+	u_int32_t snaplen;
+	u_int32_t network;
+} __attribute__((packed));
+
+struct pcaprec_hdr {
+	u_int32_t ts_sec;
+	u_int32_t ts_usec;
+	u_int32_t incl_len;
+	u_int32_t orig_len;
+} __attribute__((packed));
+
+struct fake_linux_lapd_header {
+        u_int16_t pkttype;
+	u_int16_t hatype;
+	u_int16_t halen;
+	u_int64_t addr;
+	int16_t protocol;
+} __attribute__((packed));
+
+struct lapd_header {
+	u_int8_t ea1 : 1;
+	u_int8_t cr : 1;
+	u_int8_t sapi : 6;
+	u_int8_t ea2 : 1;
+	u_int8_t tei : 7;
+	u_int8_t control_foo; /* fake UM's ... */
+} __attribute__((packed));
+
+static_assert(offsetof(struct fake_linux_lapd_header, hatype) == 2,    hatype_offset);
+static_assert(offsetof(struct fake_linux_lapd_header, halen) == 4,     halen_offset);
+static_assert(offsetof(struct fake_linux_lapd_header, addr) == 6,      addr_offset);
+static_assert(offsetof(struct fake_linux_lapd_header, protocol) == 14, proto_offset);
+static_assert(sizeof(struct fake_linux_lapd_header) == 16,	       lapd_header_size);
+
+
+static int pcap_fd = -1;
+
+void e1_set_pcap_fd(int fd)
+{
+	int ret;
+	struct pcap_hdr header = {
+		.magic_number	= 0xa1b2c3d4,
+		.version_major	= 2,
+		.version_minor	= 4,
+		.thiszone	= 0,
+		.sigfigs	= 0,
+		.snaplen	= 65535,
+		.network	= DLT_LINUX_LAPD,
+	};
+
+	pcap_fd = fd;
+	ret = write(pcap_fd, &header, sizeof(header));
+}
+
+/* This currently only works for the D-Channel */
+static void write_pcap_packet(int direction, int sapi, int tei,
+			      struct msgb *msg) {
+	if (pcap_fd < 0)
+		return;
+
+	int ret;
+	time_t cur_time;
+	struct tm *tm;
+
+	struct fake_linux_lapd_header header = {
+		.pkttype	= 4,
+		.hatype		= 0,
+		.halen		= 0,
+		.addr		= direction == PCAP_OUTPUT ? 0x0 : 0x1,
+		.protocol	= ntohs(48),
+	};
+
+	struct lapd_header lapd_header = {
+		.ea1		= 0,
+		.cr		= direction == PCAP_OUTPUT ? 1 : 0,
+		.sapi		= sapi & 0x3F,
+		.ea2		= 1,
+		.tei		= tei & 0x7F,
+		.control_foo	= 0x03 /* UI */,
+	};	
+
+	struct pcaprec_hdr payload_header = {
+		.ts_sec	    = 0,
+		.ts_usec    = 0,
+		.incl_len   = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header)
+				+ sizeof(struct lapd_header),
+		.orig_len   = msgb_l2len(msg) + sizeof(struct fake_linux_lapd_header)
+				+ sizeof(struct lapd_header),
+	};
+
+
+	cur_time = time(NULL);
+	tm = localtime(&cur_time);
+	payload_header.ts_sec = mktime(tm);
+
+	ret = write(pcap_fd, &payload_header, sizeof(payload_header));
+	ret = write(pcap_fd, &header, sizeof(header));
+	ret = write(pcap_fd, &lapd_header, sizeof(lapd_header));
+	ret = write(pcap_fd, msg->l2h, msgb_l2len(msg));
+}
+
+static const char *sign_types[] = {
+	[E1INP_SIGN_NONE]	= "None",
+	[E1INP_SIGN_OML]	= "OML",
+	[E1INP_SIGN_RSL]	= "RSL",
+};
+const char *e1inp_signtype_name(enum e1inp_sign_type tp)
+{
+	if (tp >= ARRAY_SIZE(sign_types))
+		return "undefined";
+	return sign_types[tp];
+}
+
+static const char *ts_types[] = {
+	[E1INP_TS_TYPE_NONE]	= "None",
+	[E1INP_TS_TYPE_SIGN]	= "Signalling",
+	[E1INP_TS_TYPE_TRAU]	= "TRAU",
+};
+
+const char *e1inp_tstype_name(enum e1inp_ts_type tp)
+{
+	if (tp >= ARRAY_SIZE(ts_types))
+		return "undefined";
+	return ts_types[tp];
+}
+
+/* callback when a TRAU frame was received */
+static int subch_cb(struct subch_demux *dmx, int ch, u_int8_t *data, int len,
+		    void *_priv)
+{
+	struct e1inp_ts *e1i_ts = _priv;
+	struct gsm_e1_subslot src_ss;
+
+	src_ss.e1_nr = e1i_ts->line->num;
+	src_ss.e1_ts = e1i_ts->num;
+	src_ss.e1_ts_ss = ch;
+
+	return trau_mux_input(&src_ss, data, len);
+}
+
+int abis_rsl_sendmsg(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link;
+	struct e1inp_driver *e1inp_driver;
+	struct e1inp_ts *e1i_ts;
+
+	msg->l2h = msg->data;
+
+	if (!msg->trx) {
+		LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx == NULL: %s\n",
+			hexdump(msg->data, msg->len));
+		talloc_free(msg);
+		return -EINVAL;
+	} else if (!msg->trx->rsl_link) {
+		LOGP(DRSL, LOGL_ERROR, "rsl_sendmsg: msg->trx->rsl_link == NULL: %s\n",
+			hexdump(msg->data, msg->len));
+		talloc_free(msg);
+		return -EIO;
+	}
+
+	sign_link = msg->trx->rsl_link;
+	e1i_ts = sign_link->ts;
+	if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) {
+		/* notify the driver we have something to write */
+		e1inp_driver = sign_link->ts->line->driver;
+		e1inp_driver->want_write(e1i_ts);
+	}
+	msgb_enqueue(&sign_link->tx_list, msg);
+
+	/* dump it */
+	write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg);
+
+	return 0;
+}
+
+int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml)
+{
+	struct e1inp_sign_link *sign_link;
+	struct e1inp_driver *e1inp_driver;
+	struct e1inp_ts *e1i_ts;
+
+	msg->l2h = msg->data;
+
+	if (!msg->trx || !msg->trx->bts || !msg->trx->bts->oml_link) {
+		LOGP(DNM, LOGL_ERROR, "nm_sendmsg: msg->trx == NULL\n");
+		return -EINVAL;
+	}
+
+	/* Check for TRX-specific OML link first */
+	if (to_trx_oml) {
+		if (!msg->trx->oml_link)
+			return -ENODEV;
+		sign_link = msg->trx->oml_link;
+	} else
+		sign_link = msg->trx->bts->oml_link;
+
+	e1i_ts = sign_link->ts;
+	if (!bsc_timer_pending(&e1i_ts->sign.tx_timer)) {
+		/* notify the driver we have something to write */
+		e1inp_driver = sign_link->ts->line->driver;
+		e1inp_driver->want_write(e1i_ts);
+	}
+	msgb_enqueue(&sign_link->tx_list, msg);
+
+	/* dump it */
+	write_pcap_packet(PCAP_OUTPUT, sign_link->sapi, sign_link->tei, msg);
+
+	return 0;
+}
+
+/* Timeslot */
+
+/* configure and initialize one e1inp_ts */
+int e1inp_ts_config(struct e1inp_ts *ts, struct e1inp_line *line,
+		    enum e1inp_ts_type type)
+{
+	if (ts->type == type && ts->line && line)
+		return 0;
+
+	ts->type = type;
+	ts->line = line;
+
+	switch (type) {
+	case E1INP_TS_TYPE_SIGN:
+		if (line && line->driver)
+			ts->sign.delay = line->driver->default_delay;
+		else
+			ts->sign.delay = 100000;
+		INIT_LLIST_HEAD(&ts->sign.sign_links);
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		subchan_mux_init(&ts->trau.mux);
+		ts->trau.demux.out_cb = subch_cb;
+		ts->trau.demux.data = ts;
+		subch_demux_init(&ts->trau.demux);
+		break;
+	default:
+		LOGP(DMI, LOGL_ERROR, "unsupported E1 timeslot type %u\n",
+			ts->type);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+struct e1inp_line *e1inp_line_get(u_int8_t e1_nr)
+{
+	struct e1inp_line *e1i_line;
+
+	/* iterate over global list of e1 lines */
+	llist_for_each_entry(e1i_line, &e1inp_line_list, list) {
+		if (e1i_line->num == e1_nr)
+			return e1i_line;
+	}
+	return NULL;
+}
+
+struct e1inp_line *e1inp_line_create(u_int8_t e1_nr, const char *driver_name)
+{
+	struct e1inp_driver *driver;
+	struct e1inp_line *line;
+	int i;
+
+	line = e1inp_line_get(e1_nr);
+	if (line) {
+		LOGP(DINP, LOGL_ERROR, "E1 Line %u already exists\n",
+		     e1_nr);
+		return NULL;
+	}
+
+	driver = e1inp_driver_find(driver_name);
+	if (!driver) {
+		LOGP(DINP, LOGL_ERROR, "No such E1 driver '%s'\n",
+		     driver_name);
+		return NULL;
+	}
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return NULL;
+
+	line->driver = driver;
+
+	line->num = e1_nr;
+	for (i = 0; i < NUM_E1_TS; i++) {
+		line->ts[i].num = i+1;
+		line->ts[i].line = line;
+	}
+	llist_add_tail(&line->list, &e1inp_line_list);
+
+	return line;
+}
+
+#if 0
+struct e1inp_line *e1inp_line_get_create(u_int8_t e1_nr)
+{
+	struct e1inp_line *line;
+	int i;
+
+	line = e1inp_line_get(e1_nr);
+	if (line)
+		return line;
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return NULL;
+
+	line->num = e1_nr;
+	for (i = 0; i < NUM_E1_TS; i++) {
+		line->ts[i].num = i+1;
+		line->ts[i].line = line;
+	}
+	llist_add_tail(&line->list, &e1inp_line_list);
+
+	return line;
+}
+#endif
+
+static struct e1inp_ts *e1inp_ts_get(u_int8_t e1_nr, u_int8_t ts_nr)
+{
+	struct e1inp_line *e1i_line;
+
+	e1i_line = e1inp_line_get(e1_nr);
+	if (!e1i_line)
+		return NULL;
+
+	return &e1i_line->ts[ts_nr-1];
+}
+
+struct subch_mux *e1inp_get_mux(u_int8_t e1_nr, u_int8_t ts_nr)
+{
+	struct e1inp_ts *e1i_ts = e1inp_ts_get(e1_nr, ts_nr);
+
+	if (!e1i_ts)
+		return NULL;
+
+	return &e1i_ts->trau.mux;
+}
+
+/* Signalling Link */
+
+struct e1inp_sign_link *e1inp_lookup_sign_link(struct e1inp_ts *e1i,
+					 	u_int8_t tei, u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+
+	llist_for_each_entry(link, &e1i->sign.sign_links, list) {
+		if (link->sapi == sapi && link->tei == tei)
+			return link;
+	}
+
+	return NULL;
+}
+
+/* create a new signalling link in a E1 timeslot */
+
+struct e1inp_sign_link *
+e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type,
+			struct gsm_bts_trx *trx, u_int8_t tei,
+			u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+
+	if (ts->type != E1INP_TS_TYPE_SIGN)
+		return NULL;
+
+	link = talloc_zero(tall_sigl_ctx, struct e1inp_sign_link);
+	if (!link)
+		return NULL;
+
+	link->ts = ts;
+	link->type = type;
+	INIT_LLIST_HEAD(&link->tx_list);
+	link->trx = trx;
+	link->tei = tei;
+	link->sapi = sapi;
+
+	llist_add_tail(&link->list, &ts->sign.sign_links);
+
+	return link;
+}
+
+void e1inp_sign_link_destroy(struct e1inp_sign_link *link)
+{
+	struct msgb *msg;
+
+	llist_del(&link->list);
+	while (!llist_empty(&link->tx_list)) {
+		msg = msgb_dequeue(&link->tx_list);
+		msgb_free(msg);
+	}
+
+	if (link->ts->type == E1INP_TS_TYPE_SIGN)
+		bsc_del_timer(&link->ts->sign.tx_timer);
+
+	talloc_free(link);
+}
+
+/* the E1 driver tells us he has received something on a TS */
+int e1inp_rx_ts(struct e1inp_ts *ts, struct msgb *msg,
+		u_int8_t tei, u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+	struct gsm_bts *bts;
+	int ret;
+
+	switch (ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		/* consult the list of signalling links */
+		write_pcap_packet(PCAP_INPUT, sapi, tei, msg);
+		link = e1inp_lookup_sign_link(ts, tei, sapi);
+		if (!link) {
+			LOGP(DMI, LOGL_ERROR, "didn't find signalling link for "
+				"tei %d, sapi %d\n", tei, sapi);
+			return -EINVAL;
+		}
+
+		log_set_context(BSC_CTX_BTS, link->trx->bts);
+		switch (link->type) {
+		case E1INP_SIGN_OML:
+			msg->trx = link->trx;
+			bts = msg->trx->bts;
+			ret = bts->model->oml_rcvmsg(msg);
+			break;
+		case E1INP_SIGN_RSL:
+			msg->trx = link->trx;
+			ret = abis_rsl_rcvmsg(msg);
+			break;
+		default:
+			ret = -EINVAL;
+			LOGP(DMI, LOGL_ERROR, "unknown link type %u\n", link->type);
+			break;
+		}
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		ret = subch_demux_in(&ts->trau.demux, msg->l2h, msgb_l2len(msg));
+		break;
+	default:
+		ret = -EINVAL;
+		LOGP(DMI, LOGL_ERROR, "unknown TS type %u\n", ts->type);
+		break;
+	}
+
+	return ret;
+}
+
+#define TSX_ALLOC_SIZE 4096
+
+/* called by driver if it wants to transmit on a given TS */
+struct msgb *e1inp_tx_ts(struct e1inp_ts *e1i_ts,
+			 struct e1inp_sign_link **sign_link)
+{
+	struct e1inp_sign_link *link;
+	struct msgb *msg = NULL;
+	int len;
+
+	switch (e1i_ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		/* FIXME: implement this round robin */
+		llist_for_each_entry(link, &e1i_ts->sign.sign_links, list) {
+			msg = msgb_dequeue(&link->tx_list);
+			if (msg) {
+				if (sign_link)
+					*sign_link = link;
+				break;
+			}
+		}
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		msg = msgb_alloc(TSX_ALLOC_SIZE, "TRAU_TX");
+		if (!msg)
+			return NULL;
+		len = subchan_mux_out(&e1i_ts->trau.mux, msg->data, 40);
+		msgb_put(msg, 40);
+		break;
+	default:
+		LOGP(DMI, LOGL_ERROR, "unsupported E1 TS type %u\n", e1i_ts->type);
+		return NULL;
+	}
+	return msg;
+}
+
+/* called by driver in case some kind of link state event */
+int e1inp_event(struct e1inp_ts *ts, int evt, u_int8_t tei, u_int8_t sapi)
+{
+	struct e1inp_sign_link *link;
+	struct input_signal_data isd;
+
+	link = e1inp_lookup_sign_link(ts, tei, sapi);
+	if (!link)
+		return -EINVAL;
+
+	isd.link_type = link->type;
+	isd.trx = link->trx;
+	isd.tei = tei;
+	isd.sapi = sapi;
+
+	/* report further upwards */
+	dispatch_signal(SS_INPUT, evt, &isd);
+	return 0;
+}
+
+/* register a driver with the E1 core */
+int e1inp_driver_register(struct e1inp_driver *drv)
+{
+	llist_add_tail(&drv->list, &e1inp_driver_list);
+	return 0;
+}
+
+struct e1inp_driver *e1inp_driver_find(const char *name)
+{
+	struct e1inp_driver *drv;
+
+	llist_for_each_entry(drv, &e1inp_driver_list, list) {
+		if (!strcasecmp(name, drv->name))
+			return drv;
+	}
+	return NULL;
+}
+
+int e1inp_line_update(struct e1inp_line *line)
+{
+	struct input_signal_data isd;
+	int rc;
+
+	if (line->driver && line->driver->line_update)
+		rc = line->driver->line_update(line);
+	else
+		rc = 0;
+
+	/* Send a signal to anyone who is interested in new lines being
+	 * configured */
+	memset(&isd, 0, sizeof(isd));
+	isd.line = line;
+	dispatch_signal(SS_INPUT, S_INP_LINE_INIT, &isd);
+
+	return rc;
+}
+
+static int e1i_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	if (subsys != SS_GLOBAL ||
+	    signal != S_GLOBAL_SHUTDOWN)
+		return 0;
+
+	if (pcap_fd) {
+		close(pcap_fd);
+		pcap_fd = -1;
+	}
+
+	return 0;
+}
+
+void e1inp_misdn_init(void);
+void e1inp_dahdi_init(void);
+
+void e1inp_init(void)
+{
+	tall_sigl_ctx = talloc_named_const(tall_bsc_ctx, 1,
+					   "e1inp_sign_link");
+	register_signal_handler(SS_GLOBAL, e1i_sig_cb, NULL);
+
+	e1inp_misdn_init();
+#ifdef HAVE_DAHDI_USER_H
+	e1inp_dahdi_init();
+#endif
+}
diff --git a/src/libabis/e1_input_vty.c b/src/libabis/e1_input_vty.c
new file mode 100644
index 0000000..66bf655
--- /dev/null
+++ b/src/libabis/e1_input_vty.c
@@ -0,0 +1,102 @@
+/* OpenBSC E1 vty interface */
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/talloc.h>
+#include <openbsc/vty.h>
+#include <openbsc/debug.h>
+
+#include "../../bscconfig.h"
+
+#define E1_DRIVER_NAMES		"(misdn|dahdi)"
+#define E1_DRIVER_HELP		"mISDN supported E1 Card\n" \
+				"DAHDI supported E1/T1/J1 Card\n"
+
+DEFUN(cfg_e1line_driver, cfg_e1_line_driver_cmd,
+	"e1_line <0-255> driver " E1_DRIVER_NAMES,
+	"Configure E1/T1/J1 Line\n" "Line Number\n" "Set driver for this line\n"
+	E1_DRIVER_HELP)
+{
+	struct e1inp_line *line;
+	int e1_nr = atoi(argv[0]);
+
+	line = e1inp_line_get(e1_nr);
+	if (line) {
+		vty_out(vty, "%% Line %d already exists%s", e1_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	line = e1inp_line_create(e1_nr, argv[1]);
+	if (!line) {
+		vty_out(vty, "%% Error creating line %d%s", e1_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_e1inp, cfg_e1inp_cmd,
+	"e1_input",
+	"Configure E1/T1/J1 TDM input\n")
+{
+	vty->node = E1INP_NODE;
+
+	return CMD_SUCCESS;
+}
+
+static int e1inp_config_write(struct vty *vty)
+{
+	struct e1inp_line *line;
+
+	vty_out(vty, "e1_input%s", VTY_NEWLINE);
+
+	llist_for_each_entry(line, &e1inp_line_list, list) {
+		vty_out(vty, " e1_line %u driver %s%s", line->num,
+			line->driver->name, VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+struct cmd_node e1inp_node = {
+	E1INP_NODE,
+	"%s(e1_input)#",
+	1,
+};
+
+int e1inp_vty_init(void)
+{
+	install_element(CONFIG_NODE, &cfg_e1inp_cmd);
+	install_node(&e1inp_node, e1inp_config_write);
+	install_element(E1INP_NODE, &cfg_e1_line_driver_cmd);
+
+	return 0;
+}
diff --git a/src/libabis/input/dahdi.c b/src/libabis/input/dahdi.c
new file mode 100644
index 0000000..572bb5a
--- /dev/null
+++ b/src/libabis/input/dahdi.c
@@ -0,0 +1,494 @@
+/* OpenBSC Abis input driver for DAHDI */
+
+/* (C) 2008-2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "../../../bscconfig.h"
+
+#ifdef HAVE_DAHDI_USER_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <dahdi/user.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+
+#include "lapd.h"
+
+#define TS1_ALLOC_SIZE	300
+
+/* Corresponds to dahdi/user.h, only PRI related events */
+static const struct value_string dahdi_evt_names[] = {
+	{ DAHDI_EVENT_NONE,		"NONE" },
+	{ DAHDI_EVENT_ALARM,		"ALARM" },
+	{ DAHDI_EVENT_NOALARM,		"NOALARM" },
+	{ DAHDI_EVENT_ABORT,		"HDLC ABORT" },
+	{ DAHDI_EVENT_OVERRUN,		"HDLC OVERRUN" },
+	{ DAHDI_EVENT_BADFCS,		"HDLC BAD FCS" },
+	{ DAHDI_EVENT_REMOVED,		"REMOVED" },
+	{ 0, NULL }
+};
+
+static void handle_dahdi_exception(struct e1inp_ts *ts)
+{
+	int rc, evt;
+	struct input_signal_data isd;
+
+	rc = ioctl(ts->driver.dahdi.fd.fd, DAHDI_GETEVENT, &evt);
+	if (rc < 0)
+		return;
+
+	LOGP(DMI, LOGL_NOTICE, "Line %u(%s) / TS %u DAHDI EVENT %s\n",
+		ts->line->num, ts->line->name, ts->num,
+		get_value_string(dahdi_evt_names, evt));
+
+	isd.line = ts->line;
+
+	switch (evt) {
+	case DAHDI_EVENT_ALARM:
+		/* we should notify the code that the line is gone */
+		dispatch_signal(SS_INPUT, S_INP_LINE_ALARM, &isd);
+		break;
+	case DAHDI_EVENT_NOALARM:
+		/* alarm has gone, we should re-start the SABM requests */
+		dispatch_signal(SS_INPUT, S_INP_LINE_NOALARM, &isd);
+		break;
+	}
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "DAHDI TS1");
+	lapd_mph_type prim;
+	unsigned int sapi, tei;
+	int ilen, ret;
+	uint8_t *idata;
+
+	if (!msg)
+		return -ENOMEM;
+
+	ret = read(bfd->fd, msg->data, TS1_ALLOC_SIZE - 16);
+	if (ret == -1)
+		handle_dahdi_exception(e1i_ts);
+	else if (ret < 0) {
+		perror("read ");
+	}
+	msgb_put(msg, ret - 2);
+	if (ret <= 3) {
+		perror("read ");
+	}
+
+	sapi = msg->data[0] >> 2;
+	tei = msg->data[1] >> 1;
+
+	DEBUGP(DMI, "<= len = %d, sapi(%d) tei(%d)", ret, sapi, tei);
+
+	idata = lapd_receive(e1i_ts->driver.dahdi.lapd, msg->data, msg->len, &ilen, &prim);
+	if (!idata && prim == 0)
+		return -EIO;
+
+	msgb_pull(msg, 2);
+
+	DEBUGP(DMI, "prim %08x\n", prim);
+
+	switch (prim) {
+	case 0:
+		break;
+	case LAPD_MPH_ACTIVATE_IND:
+		DEBUGP(DMI, "MPH_ACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei);
+		ret = e1inp_event(e1i_ts, S_INP_TEI_UP, tei, sapi);
+		break;
+	case LAPD_MPH_DEACTIVATE_IND:
+		DEBUGP(DMI, "MPH_DEACTIVATE_IND: sapi(%d) tei(%d)\n", sapi, tei);
+		ret = e1inp_event(e1i_ts, S_INP_TEI_DN, tei, sapi);
+		break;
+	case LAPD_DL_DATA_IND:
+	case LAPD_DL_UNITDATA_IND:
+		if (prim == LAPD_DL_DATA_IND)
+			msg->l2h = msg->data + 2;
+		else
+			msg->l2h = msg->data + 1;
+		DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret));
+		ret = e1inp_rx_ts(e1i_ts, msg, tei, sapi);
+		break;
+	default:
+		printf("ERROR: unknown prim\n");
+		break;
+	}
+
+	DEBUGP(DMI, "Returned ok\n");
+	return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+	/* We never include the DAHDI B-Channel FD into the
+	 * writeset, since it doesn't support poll() based
+	 * write flow control */
+	if (e1i_ts->type == E1INP_TS_TYPE_TRAU) {
+		fprintf(stderr, "Trying to write TRAU ts\n");
+		return 0;
+	}
+
+	e1i_ts->driver.dahdi.fd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+	struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+	/* trigger write of ts1, due to tx delay timer */
+	ts_want_write(e1i_ts);
+}
+
+static void dahdi_write_msg(uint8_t *data, int len, void *cbdata)
+{
+	struct bsc_fd *bfd = cbdata;
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	int ret;
+
+	ret = write(bfd->fd, data, len + 2);
+	if (ret == -1)
+		handle_dahdi_exception(e1i_ts);
+	else if (ret < 0)
+		LOGP(DMI, LOGL_NOTICE, "%s write failed %d\n", __func__, ret);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *sign_link;
+	struct msgb *msg;
+
+	bfd->when &= ~BSC_FD_WRITE;
+
+	/* get the next msg for this timeslot */
+	msg = e1inp_tx_ts(e1i_ts, &sign_link);
+	if (!msg) {
+		/* no message after tx delay timer */
+		return 0;
+	}
+
+	DEBUGP(DMI, "TX: %s\n", hexdump(msg->data, msg->len));
+	lapd_transmit(e1i_ts->driver.dahdi.lapd, sign_link->tei,
+		      sign_link->sapi, msg->data, msg->len);
+	msgb_free(msg);
+
+	/* set tx delay timer for next event */
+	e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+	e1i_ts->sign.tx_timer.data = e1i_ts;
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, 50000);
+
+	return 0;
+}
+
+
+static int invertbits = 1;
+
+static u_int8_t flip_table[256];
+
+static void init_flip_bits(void)
+{
+        int i,k;
+
+        for (i = 0 ; i < 256 ; i++) {
+                u_int8_t sample = 0 ;
+                for (k = 0; k<8; k++) {
+                        if ( i & 1 << k ) sample |= 0x80 >>  k;
+                }
+                flip_table[i] = sample;
+        }
+}
+
+static u_int8_t * flip_buf_bits ( u_int8_t * buf , int len)
+{
+        int i;
+        u_int8_t * start = buf;
+
+        for (i = 0 ; i < len; i++) {
+                buf[i] = flip_table[(u_int8_t)buf[i]];
+        }
+
+        return start;
+}
+
+#define D_BCHAN_TX_GRAN 160
+/* write to a B channel TS */
+static int handle_tsX_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	u_int8_t tx_buf[D_BCHAN_TX_GRAN];
+	struct subch_mux *mx = &e1i_ts->trau.mux;
+	int ret;
+
+	ret = subchan_mux_out(mx, tx_buf, D_BCHAN_TX_GRAN);
+
+	if (ret != D_BCHAN_TX_GRAN) {
+		fprintf(stderr, "Huh, got ret of %d\n", ret);
+		if (ret < 0)
+			return ret;
+	}
+
+	DEBUGP(DMIB, "BCHAN TX: %s\n",
+		hexdump(tx_buf, D_BCHAN_TX_GRAN));
+
+	if (invertbits) {
+		flip_buf_bits(tx_buf, ret);
+	}
+
+	ret = write(bfd->fd, tx_buf, ret);
+	if (ret < D_BCHAN_TX_GRAN)
+		fprintf(stderr, "send returns %d instead of %d\n", ret,
+			D_BCHAN_TX_GRAN);
+
+	return ret;
+}
+
+#define D_TSX_ALLOC_SIZE (D_BCHAN_TX_GRAN)
+/* FIXME: read from a B channel TS */
+static int handle_tsX_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct msgb *msg = msgb_alloc(D_TSX_ALLOC_SIZE, "DAHDI TSx");
+	int ret;
+
+	if (!msg)
+		return -ENOMEM;
+
+	ret = read(bfd->fd, msg->data, D_TSX_ALLOC_SIZE);
+	if (ret < 0 || ret != D_TSX_ALLOC_SIZE) {
+		fprintf(stderr, "read error  %d %s\n", ret, strerror(errno));
+		return ret;
+	}
+
+	if (invertbits) {
+		flip_buf_bits(msg->data, ret);
+	}
+
+	msgb_put(msg, ret);
+
+	msg->l2h = msg->data;
+	DEBUGP(DMIB, "BCHAN RX: %s\n",
+		hexdump(msgb_l2(msg), ret));
+	ret = e1inp_rx_ts(e1i_ts, msg, 0, 0);
+	/* physical layer indicates that data has been sent,
+	 * we thus can send some more data */
+	ret = handle_tsX_write(bfd);
+	msgb_free(msg);
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int dahdi_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	unsigned int idx = ts_nr-1;
+	struct e1inp_ts *e1i_ts = &line->ts[idx];
+	int rc = 0;
+
+	switch (e1i_ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		if (what & BSC_FD_EXCEPT)
+			handle_dahdi_exception(e1i_ts);
+		if (what & BSC_FD_READ)
+			rc = handle_ts1_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_ts1_write(bfd);
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		if (what & BSC_FD_EXCEPT)
+			handle_dahdi_exception(e1i_ts);
+		if (what & BSC_FD_READ)
+			rc = handle_tsX_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_tsX_write(bfd);
+		/* We never include the DAHDI B-Channel FD into the
+		 * writeset, since it doesn't support poll() based
+		 * write flow control */
+		break;
+	default:
+		fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type);
+		break;
+	}
+
+	return rc;
+}
+
+static int dahdi_e1_line_update(struct e1inp_line *line);
+
+struct e1inp_driver dahdi_driver = {
+	.name = "dahdi",
+	.want_write = ts_want_write,
+	.line_update = &dahdi_e1_line_update,
+};
+
+void dahdi_set_bufinfo(int fd, int as_sigchan)
+{
+	struct dahdi_bufferinfo bi;
+	int x = 0;
+
+	if (ioctl(fd, DAHDI_GET_BUFINFO, &bi)) {
+		fprintf(stderr, "Error getting bufinfo\n");
+		exit(-1);
+	}
+
+	if (as_sigchan) {
+		bi.numbufs = 4;
+		bi.bufsize = 512;
+	} else {
+		bi.numbufs = 8;
+		bi.bufsize = D_BCHAN_TX_GRAN;
+		bi.txbufpolicy = DAHDI_POLICY_WHEN_FULL;
+	}
+
+	if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) {
+		fprintf(stderr, "Error setting bufinfo\n");
+		exit(-1);
+	}
+
+	if (!as_sigchan) {
+		if (ioctl(fd, DAHDI_AUDIOMODE, &x)) {
+			fprintf(stderr, "Error setting bufinfo\n");
+			exit(-1);
+		}
+	} else {
+		int one = 1;
+		ioctl(fd, DAHDI_HDLCFCSMODE, &one);
+		/* we cannot reliably check for the ioctl return value here
+		 * as this command will fail if the slot _already_ was a
+		 * signalling slot before :( */
+	}
+}
+
+static int dahdi_e1_setup(struct e1inp_line *line)
+{
+	int ts, ret;
+
+	/* TS0 is CRC4, don't need any fd for it */
+	for (ts = 1; ts < NUM_E1_TS; ts++) {
+		unsigned int idx = ts-1;
+		char openstr[128];
+		struct e1inp_ts *e1i_ts = &line->ts[idx];
+		struct bsc_fd *bfd = &e1i_ts->driver.dahdi.fd;
+
+		bfd->data = line;
+		bfd->priv_nr = ts;
+		bfd->cb = dahdi_fd_cb;
+		snprintf(openstr, sizeof(openstr), "/dev/dahdi/%d", ts);
+
+		switch (e1i_ts->type) {
+		case E1INP_TS_TYPE_NONE:
+			continue;
+			break;
+		case E1INP_TS_TYPE_SIGN:
+			bfd->fd = open(openstr, O_RDWR | O_NONBLOCK);
+			if (bfd->fd == -1) {
+				fprintf(stderr, "%s could not open %s %s\n",
+					__func__, openstr, strerror(errno));
+				exit(-1);
+			}
+			bfd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+			dahdi_set_bufinfo(bfd->fd, 1);
+			e1i_ts->driver.dahdi.lapd = lapd_instance_alloc(1, dahdi_write_msg, bfd);
+			break;
+		case E1INP_TS_TYPE_TRAU:
+			bfd->fd = open(openstr, O_RDWR | O_NONBLOCK);
+			if (bfd->fd == -1) {
+				fprintf(stderr, "%s could not open %s %s\n",
+					__func__, openstr, strerror(errno));
+				exit(-1);
+			}
+			dahdi_set_bufinfo(bfd->fd, 0);
+			/* We never include the DAHDI B-Channel FD into the
+			 * writeset, since it doesn't support poll() based
+			 * write flow control */
+			bfd->when = BSC_FD_READ | BSC_FD_EXCEPT;// | BSC_FD_WRITE;
+			break;
+		}
+
+		if (bfd->fd < 0) {
+			fprintf(stderr, "%s could not open %s %s\n",
+				__func__, openstr, strerror(errno));
+			return bfd->fd;
+		}
+
+		ret = bsc_register_fd(bfd);
+		if (ret < 0) {
+			fprintf(stderr, "could not register FD: %s\n",
+				strerror(ret));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int dahdi_e1_line_update(struct e1inp_line *line)
+{
+	if (line->driver != &dahdi_driver)
+		return -EINVAL;
+
+	return dahdi_e1_setup(line);
+}
+
+int e1inp_dahdi_init(void)
+{
+	init_flip_bits();
+
+	/* register the driver with the core */
+	return e1inp_driver_register(&dahdi_driver);
+}
+
+#endif /* HAVE_DAHDI_USER_H */
diff --git a/src/libabis/input/hsl.c b/src/libabis/input/hsl.c
new file mode 100644
index 0000000..1afe82b
--- /dev/null
+++ b/src/libabis/input/hsl.c
@@ -0,0 +1,460 @@
+/* OpenBSC Abis input driver for HSL Femto */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2011 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* HSL uses a much more primitive/simplified version of the IPA multiplex.
+ *
+ * They have taken out the nice parts like the ID_GET / ID_RESP for resolving
+ * the UNIT ID, as well as the keepalive ping/pong messages.  Furthermore, the
+ * Stream Identifiers are fixed on the BTS side (RSL always 0, OML always 0xff)
+ * and both OML+RSL share a single TCP connection.
+ *
+ * Other oddities include the encapsulation of BSSGP messages in the L3_INFO IE
+ * of RSL
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+
+#define HSL_TCP_PORT	2500
+#define HSL_PROTO_DEBUG	0xdd
+
+#define PRIV_OML 1
+#define PRIV_RSL 2
+
+/* data structure for one E1 interface with A-bis */
+struct hsl_e1_handle {
+	struct bsc_fd listen_fd;
+	struct gsm_network *gsmnet;
+};
+
+static struct hsl_e1_handle *e1h;
+
+
+#define TS1_ALLOC_SIZE	900
+
+#define OML_UP		0x0001
+#define RSL_UP		0x0002
+
+int hsl_drop_oml(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	struct e1inp_ts *ts;
+	struct e1inp_line *line;
+	struct bsc_fd *bfd;
+
+	if (!bts || !bts->oml_link)
+		return -1;
+
+	/* send OML down */
+	ts = bts->oml_link->ts;
+	line = ts->line;
+	e1inp_event(ts, S_INP_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi);
+
+	bfd = &ts->driver.ipaccess.fd;
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* clean up OML and RSL */
+	e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = NULL;
+	e1inp_sign_link_destroy(bts->c0->rsl_link);
+	bts->c0->rsl_link = NULL;
+	bts->ip_access.flags = 0;
+
+	/* kill the E1 line now... as we have no one left to use it */
+	talloc_free(line);
+
+	return -1;
+}
+
+static int hsl_drop_ts_fd(struct e1inp_ts *ts, struct bsc_fd *bfd)
+{
+	struct e1inp_sign_link *link, *link2;
+	int bts_nr = -1;
+
+	llist_for_each_entry_safe(link, link2, &ts->sign.sign_links, list) {
+		bts_nr = link->trx->bts->bts_nr;
+		e1inp_sign_link_destroy(link);
+	}
+
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	talloc_free(ts->line);
+
+	return bts_nr;
+}
+
+struct gsm_bts *find_bts_by_serno(struct gsm_network *net, unsigned long serno)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (bts->type != GSM_BTS_TYPE_HSL_FEMTO)
+			continue;
+
+		if (serno == bts->hsl.serno)
+			return bts;
+	}
+
+	return NULL;
+}
+
+
+static int process_hsl_rsl(struct msgb *msg, struct e1inp_line *line)
+{
+	char serno_buf[16];
+	uint8_t serno_len;
+	unsigned long serno;
+	struct gsm_bts *bts;
+
+	switch (msg->l2h[1]) {
+	case 0x80:
+		/*, contains Serial Number + SW version */
+		if (msg->l2h[2] != 0xc0)
+			break;
+		serno_len = msg->l2h[3];
+		if (serno_len > sizeof(serno_buf)-1)
+			serno_len = sizeof(serno_buf)-1;
+		memcpy(serno_buf, msg->l2h+4, serno_len);
+		serno_buf[serno_len] = '\0';
+		serno = strtoul(serno_buf, NULL, 10);
+		bts = find_bts_by_serno(e1h->gsmnet, serno);
+		if (!bts) {
+			LOGP(DINP, LOGL_ERROR, "Unable to find BTS config for "
+				"serial number %lu(%s)\n", serno, serno_buf);
+			return -EIO;
+		}
+
+		DEBUGP(DINP, "Identified HSL BTS Serial Number %lu\n", serno);
+
+		/* we shouldn't hardcode it, but HSL femto also hardcodes it... */
+		bts->oml_tei = 255;
+		bts->c0->rsl_tei = 0;
+		bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+							E1INP_SIGN_OML, bts->c0,
+							bts->oml_tei, 0);
+		bts->c0->rsl_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+							E1INP_SIGN_RSL, bts->c0,
+							bts->c0->rsl_tei, 0);
+		e1inp_event(&line->ts[PRIV_OML-1], S_INP_TEI_UP, 255, 0);
+		e1inp_event(&line->ts[PRIV_OML-1], S_INP_TEI_UP, 0, 0);
+		bts->ip_access.flags |= OML_UP;
+		bts->ip_access.flags |= (RSL_UP << 0);
+		msgb_free(msg);
+		return 1;	/* == we have taken over the msg */
+	case 0x82:
+		/* FIXME: do something with BSSGP, i.e. forward it over
+		 * NSIP to OsmoSGSN */
+		msgb_free(msg);
+		return 1;
+	}
+	return 0;
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *link;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+	int ret = 0, error;
+
+	msg = ipaccess_read_msg(bfd, &error);
+	if (!msg) {
+		if (error == 0) {
+			int ret = hsl_drop_ts_fd(e1i_ts, bfd);
+			if (ret >= 0)
+				LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n",
+					ret);
+			else
+				LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n");
+		}
+		return error;
+	}
+
+	DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == HSL_PROTO_DEBUG) {
+		LOGP(DINP, LOGL_NOTICE, "HSL debug: %s\n", msg->data + sizeof(*hh));
+		msgb_free(msg);
+		return ret;
+	}
+
+	/* HSL proprietary RSL extension */
+	if (hh->proto == 0 && (msg->l2h[0] == 0x81 || msg->l2h[0] == 0x80)) {
+		ret = process_hsl_rsl(msg, line);
+		if (ret < 0) {
+			/* FIXME: close connection */
+			hsl_drop_ts_fd(e1i_ts, bfd);
+			return ret;
+		} else if (ret == 1)
+			return 0;
+		/* else: continue... */
+	}
+#ifdef HSL_SR_1_0
+	/* HSL for whatever reason chose to use 0x81 instead of 0x80 for FOM */
+	if (hh->proto == 255 && msg->l2h[0] == (ABIS_OM_MDISC_FOM | 0x01))
+		msg->l2h[0] = ABIS_OM_MDISC_FOM;
+#endif
+	link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0);
+	if (!link) {
+		LOGP(DINP, LOGL_ERROR, "no matching signalling link for "
+			"hh->proto=0x%02x\n", hh->proto);
+		msgb_free(msg);
+		return -EIO;
+	}
+	msg->trx = link->trx;
+
+	switch (link->type) {
+	case E1INP_SIGN_RSL:
+		if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) {
+			e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+			msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr);
+		}
+		ret = abis_rsl_rcvmsg(msg);
+		break;
+	case E1INP_SIGN_OML:
+		if (!(msg->trx->bts->ip_access.flags & OML_UP)) {
+			e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+			msg->trx->bts->ip_access.flags |= OML_UP;
+		}
+		ret = abis_nm_rcvmsg(msg);
+		break;
+	default:
+		LOGP(DINP, LOGL_NOTICE, "Unknown HSL protocol class 0x%02x\n", hh->proto);
+		msgb_free(msg);
+		break;
+	}
+	return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+	e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+	struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+	/* trigger write of ts1, due to tx delay timer */
+	ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *sign_link;
+	struct msgb *msg;
+	u_int8_t proto;
+	int ret;
+
+	bfd->when &= ~BSC_FD_WRITE;
+
+	/* get the next msg for this timeslot */
+	msg = e1inp_tx_ts(e1i_ts, &sign_link);
+	if (!msg) {
+		/* no message after tx delay timer */
+		return 0;
+	}
+
+	switch (sign_link->type) {
+	case E1INP_SIGN_OML:
+		proto = IPAC_PROTO_OML;
+#ifdef HSL_SR_1_0
+		/* HSL uses 0x81 for FOM for some reason */
+		if (msg->data[0] == ABIS_OM_MDISC_FOM)
+			msg->data[0] = ABIS_OM_MDISC_FOM | 0x01;
+#endif
+		break;
+	case E1INP_SIGN_RSL:
+		proto = IPAC_PROTO_RSL;
+		break;
+	default:
+		msgb_free(msg);
+		bfd->when |= BSC_FD_WRITE; /* come back for more msg */
+		return -EINVAL;
+	}
+
+	msg->l2h = msg->data;
+	ipaccess_prepend_header(msg, sign_link->tei);
+
+	DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg)));
+
+	ret = send(bfd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	/* set tx delay timer for next event */
+	e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+	e1i_ts->sign.tx_timer.data = e1i_ts;
+
+	/* Reducing this might break the nanoBTS 900 init. */
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay);
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int hsl_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	unsigned int idx = ts_nr-1;
+	struct e1inp_ts *e1i_ts;
+	int rc = 0;
+
+	/* In case of early RSL we might not yet have a line */
+
+	if (line)
+		e1i_ts = &line->ts[idx];
+
+	if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+		if (what & BSC_FD_READ)
+			rc = handle_ts1_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_ts1_write(bfd);
+	} else
+		LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type);
+
+	return rc;
+}
+
+struct e1inp_driver hsl_driver = {
+	.name = "HSL",
+	.want_write = ts_want_write,
+	.default_delay = 0,
+};
+
+/* callback of the OML listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	int idx = 0;
+	int i;
+	struct e1inp_line *line;
+	struct e1inp_ts *e1i_ts;
+	struct bsc_fd *bfd;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		perror("accept");
+		return ret;
+	}
+	LOGP(DINP, LOGL_NOTICE, "accept()ed new HSL link from %s\n",
+		inet_ntoa(sa.sin_addr));
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line) {
+		close(ret);
+		return -ENOMEM;
+	}
+	line->driver = &hsl_driver;
+	//line->driver_data = e1h;
+	/* create virrtual E1 timeslots for signalling */
+	e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+
+	/* initialize the fds */
+	for (i = 0; i < ARRAY_SIZE(line->ts); ++i)
+		line->ts[i].driver.ipaccess.fd.fd = -1;
+
+	e1i_ts = &line->ts[idx];
+
+	bfd = &e1i_ts->driver.ipaccess.fd;
+	bfd->fd = ret;
+	bfd->data = line;
+	bfd->priv_nr = PRIV_OML;
+	bfd->cb = hsl_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(line);
+		return ret;
+	}
+
+        return ret;
+	//return e1inp_line_register(line);
+}
+
+int hsl_setup(struct gsm_network *gsmnet)
+{
+	int ret;
+
+	/* register the driver with the core */
+	/* FIXME: do this in the plugin initializer function */
+	ret = e1inp_driver_register(&hsl_driver);
+	if (ret)
+		return ret;
+
+	e1h = talloc_zero(tall_bsc_ctx, struct hsl_e1_handle);
+	if (!e1h)
+		return -ENOMEM;
+
+	e1h->gsmnet = gsmnet;
+
+	/* Listen for connections */
+	ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, 0, HSL_TCP_PORT,
+			listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
diff --git a/src/libabis/input/ipaccess.c b/src/libabis/input/ipaccess.c
new file mode 100644
index 0000000..dcf8d1a
--- /dev/null
+++ b/src/libabis/input/ipaccess.c
@@ -0,0 +1,797 @@
+/* OpenBSC Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+#include <openbsc/signal.h>
+
+#define PRIV_OML 1
+#define PRIV_RSL 2
+
+/* data structure for one E1 interface with A-bis */
+struct ia_e1_handle {
+	struct bsc_fd listen_fd;
+	struct bsc_fd rsl_listen_fd;
+	struct gsm_network *gsmnet;
+};
+
+static struct ia_e1_handle *e1h;
+
+
+#define TS1_ALLOC_SIZE	900
+
+static const u_int8_t pong[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG };
+static const u_int8_t id_ack[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK };
+static const u_int8_t id_req[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET,
+					0x01, IPAC_IDTAG_UNIT,
+					0x01, IPAC_IDTAG_MACADDR,
+					0x01, IPAC_IDTAG_LOCATION1,
+					0x01, IPAC_IDTAG_LOCATION2,
+					0x01, IPAC_IDTAG_EQUIPVERS,
+					0x01, IPAC_IDTAG_SWVERSION,
+					0x01, IPAC_IDTAG_UNITNAME,
+					0x01, IPAC_IDTAG_SERNR,
+				};
+
+static const char *idtag_names[] = {
+	[IPAC_IDTAG_SERNR]	= "Serial_Number",
+	[IPAC_IDTAG_UNITNAME]	= "Unit_Name",
+	[IPAC_IDTAG_LOCATION1]	= "Location_1",
+	[IPAC_IDTAG_LOCATION2]	= "Location_2",
+	[IPAC_IDTAG_EQUIPVERS]	= "Equipment_Version",
+	[IPAC_IDTAG_SWVERSION]	= "Software_Version",
+	[IPAC_IDTAG_IPADDR]	= "IP_Address",
+	[IPAC_IDTAG_MACADDR]	= "MAC_Address",
+	[IPAC_IDTAG_UNIT]	= "Unit_ID",
+};
+
+static const char *ipac_idtag_name(int tag)
+{
+	if (tag >= ARRAY_SIZE(idtag_names))
+		return "unknown";
+
+	return idtag_names[tag];
+}
+
+int ipaccess_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
+{
+	u_int8_t t_len;
+	u_int8_t t_tag;
+	u_int8_t *cur = buf;
+
+	memset(dec, 0, sizeof(*dec));
+
+	while (len >= 2) {
+		len -= 2;
+		t_len = *cur++;
+		t_tag = *cur++;
+
+		if (t_len > len + 1) {
+			LOGP(DMI, LOGL_ERROR, "The tag does not fit: %d\n", t_len);
+			return -1;
+		}
+
+		DEBUGPC(DMI, "%s='%s' ", ipac_idtag_name(t_tag), cur);
+
+		dec->lv[t_tag].len = t_len;
+		dec->lv[t_tag].val = cur;
+
+		cur += t_len;
+		len -= t_len;
+	}
+	return 0;
+}
+
+struct gsm_bts *find_bts_by_unitid(struct gsm_network *net,
+				   u_int16_t site_id, u_int16_t bts_id)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+
+		if (!is_ipaccess_bts(bts))
+			continue;
+
+		if (bts->ip_access.site_id == site_id &&
+		    bts->ip_access.bts_id == bts_id)
+			return bts;
+	}
+
+	return NULL;
+}
+
+static int parse_unitid(const char *str, u_int16_t *site_id, u_int16_t *bts_id,
+			u_int16_t *trx_id)
+{
+	unsigned long ul;
+	char *endptr;
+	const char *nptr;
+
+	nptr = str;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (site_id)
+		*site_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (bts_id)
+		*bts_id = ul & 0xffff;
+
+	if (*endptr++ != '/')
+		return -EINVAL;
+	
+	nptr = endptr;
+	ul = strtoul(nptr, &endptr, 10);
+	if (endptr <= nptr)
+		return -EINVAL;
+	if (trx_id)
+		*trx_id = ul & 0xffff;
+
+	return 0;
+}
+
+/* send the id ack */
+int ipaccess_send_id_ack(int fd)
+{
+	return write(fd, id_ack, sizeof(id_ack));
+}
+
+int ipaccess_send_id_req(int fd)
+{
+	return write(fd, id_req, sizeof(id_req));
+}
+
+/* base handling of the ip.access protocol */
+int ipaccess_rcvmsg_base(struct msgb *msg,
+			 struct bsc_fd *bfd)
+{
+	u_int8_t msg_type = *(msg->l2h);
+	int ret = 0;
+
+	switch (msg_type) {
+	case IPAC_MSGT_PING:
+		ret = write(bfd->fd, pong, sizeof(pong));
+		break;
+	case IPAC_MSGT_PONG:
+		DEBUGP(DMI, "PONG!\n");
+		break;
+	case IPAC_MSGT_ID_ACK:
+		DEBUGP(DMI, "ID_ACK? -> ACK!\n");
+		ret = ipaccess_send_id_ack(bfd->fd);
+		break;
+	}
+	return 0;
+}
+
+static int ipaccess_rcvmsg(struct e1inp_line *line, struct msgb *msg,
+			   struct bsc_fd *bfd)
+{
+	struct tlv_parsed tlvp;
+	u_int8_t msg_type = *(msg->l2h);
+	u_int16_t site_id = 0, bts_id = 0, trx_id = 0;
+	struct gsm_bts *bts;
+	char *unitid;
+	int len;
+
+	/* handle base messages */
+	ipaccess_rcvmsg_base(msg, bfd);
+
+	switch (msg_type) {
+	case IPAC_MSGT_ID_RESP:
+		DEBUGP(DMI, "ID_RESP ");
+		/* parse tags, search for Unit ID */
+		ipaccess_idtag_parse(&tlvp, (u_int8_t *)msg->l2h + 2,
+				 msgb_l2len(msg)-2);
+		DEBUGP(DMI, "\n");
+
+		if (!TLVP_PRESENT(&tlvp, IPAC_IDTAG_UNIT))
+			break;
+
+		len = TLVP_LEN(&tlvp, IPAC_IDTAG_UNIT);
+		if (len < 1)
+			break;
+
+		/* lookup BTS, create sign_link, ... */
+		unitid = (char *) TLVP_VAL(&tlvp, IPAC_IDTAG_UNIT);
+		unitid[len - 1] = '\0';
+		parse_unitid(unitid, &site_id, &bts_id, &trx_id);
+		bts = find_bts_by_unitid(e1h->gsmnet, site_id, bts_id);
+		if (!bts) {
+			LOGP(DINP, LOGL_ERROR, "Unable to find BTS configuration for "
+			       " %u/%u/%u, disconnecting\n", site_id, bts_id,
+				trx_id);
+			return -EIO;
+		}
+		DEBUGP(DINP, "Identified BTS %u/%u/%u\n", site_id, bts_id, trx_id);
+		if (bfd->priv_nr == PRIV_OML) {
+			/* drop any old oml connection */
+			ipaccess_drop_oml(bts);
+			bts->oml_link = e1inp_sign_link_create(&line->ts[PRIV_OML - 1],
+						  E1INP_SIGN_OML, bts->c0,
+						  bts->oml_tei, 0);
+		} else if (bfd->priv_nr == PRIV_RSL) {
+			struct e1inp_ts *e1i_ts;
+			struct bsc_fd *newbfd;
+			struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_id);
+
+			/* drop any old rsl connection */
+			ipaccess_drop_rsl(trx);
+
+			if (!bts->oml_link) {
+				bsc_unregister_fd(bfd);
+				close(bfd->fd);
+				bfd->fd = -1;
+				talloc_free(bfd);
+				return 0;
+			}
+
+			bfd->data = line = bts->oml_link->ts->line;
+			e1i_ts = &line->ts[PRIV_RSL + trx_id - 1];
+			newbfd = &e1i_ts->driver.ipaccess.fd;
+			e1inp_ts_config(e1i_ts, line, E1INP_TS_TYPE_SIGN);
+
+			trx->rsl_link = e1inp_sign_link_create(e1i_ts,
+							E1INP_SIGN_RSL, trx,
+							trx->rsl_tei, 0);
+			trx->rsl_link->ts->sign.delay = 0;
+
+			/* get rid of our old temporary bfd */
+			memcpy(newbfd, bfd, sizeof(*newbfd));
+			newbfd->priv_nr = PRIV_RSL + trx_id;
+			bsc_unregister_fd(bfd);
+			bfd->fd = -1;
+			talloc_free(bfd);
+			bsc_register_fd(newbfd);
+		}
+		break;
+	}
+	return 0;
+}
+
+#define OML_UP		0x0001
+#define RSL_UP		0x0002
+
+/*
+ * read one ipa message from the socket
+ * return NULL in case of error
+ */
+struct msgb *ipaccess_read_msg(struct bsc_fd *bfd, int *error)
+{
+	struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "Abis/IP");
+	struct ipaccess_head *hh;
+	int len, ret = 0;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	/* first read our 3-byte header */
+	hh = (struct ipaccess_head *) msg->data;
+	ret = recv(bfd->fd, msg->data, sizeof(*hh), 0);
+	if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret != sizeof(*hh)) {
+		if (errno != EAGAIN)
+			LOGP(DINP, LOGL_ERROR, "recv error %d %s\n", ret, strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msgb_put(msg, ret);
+
+	/* then read te length as specified in header */
+	msg->l2h = msg->data + sizeof(*hh);
+	len = ntohs(hh->len);
+
+	if (len < 0 || TS1_ALLOC_SIZE < len + sizeof(*hh)) {
+		LOGP(DINP, LOGL_ERROR, "Can not read this packet. %d avail\n", len);
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+
+	ret = recv(bfd->fd, msg->l2h, len, 0);
+	if (ret < len) {
+		LOGP(DINP, LOGL_ERROR, "short read! Got %d from %d\n", ret, len);
+		msgb_free(msg);
+		*error = -EIO;
+		return NULL;
+	}
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+int ipaccess_drop_oml(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	struct e1inp_ts *ts;
+	struct e1inp_line *line;
+	struct bsc_fd *bfd;
+
+	if (!bts || !bts->oml_link)
+		return -1;
+
+	/* send OML down */
+	ts = bts->oml_link->ts;
+	line = ts->line;
+	e1inp_event(ts, S_INP_TEI_DN, bts->oml_link->tei, bts->oml_link->sapi);
+
+	bfd = &ts->driver.ipaccess.fd;
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* clean up OML and RSL */
+	e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = NULL;
+	bts->ip_access.flags = 0;
+
+	/* drop all RSL connections too */
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		ipaccess_drop_rsl(trx);
+
+	/* kill the E1 line now... as we have no one left to use it */
+	talloc_free(line);
+
+	return -1;
+}
+
+static int ipaccess_drop(struct e1inp_ts *ts, struct bsc_fd *bfd)
+{
+	struct e1inp_sign_link *link;
+	int bts_nr;
+
+	if (!ts) {
+		/*
+		 * If we don't have a TS this means that this is a RSL
+		 * connection but we are not past the authentication
+		 * handling yet. So we can safely delete this bfd and
+		 * wait for a reconnect.
+		 */
+		bsc_unregister_fd(bfd);
+		close(bfd->fd);
+		bfd->fd = -1;
+		talloc_free(bfd);
+		return -1;
+	}
+
+	/* attempt to find a signalling link */
+	if (ts->type == E1INP_TS_TYPE_SIGN) {
+		llist_for_each_entry(link, &ts->sign.sign_links, list) {
+			bts_nr = link->trx->bts->bts_nr;
+			/* we have issues just reconnecting RLS so we drop OML */
+			ipaccess_drop_oml(link->trx->bts);
+			return bts_nr;
+		}
+	}
+
+	/* error case */
+	LOGP(DINP, LOGL_ERROR, "Failed to find a signalling link for ts: %p\n", ts);
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+	return -1;
+}
+
+int ipaccess_drop_rsl(struct gsm_bts_trx *trx)
+{
+	struct bsc_fd *bfd;
+	struct e1inp_ts *ts;
+
+	if (!trx || !trx->rsl_link)
+		return -1;
+
+	/* send RSL down */
+	ts = trx->rsl_link->ts;
+	e1inp_event(ts, S_INP_TEI_DN, trx->rsl_link->tei, trx->rsl_link->sapi);
+
+	/* close the socket */
+	bfd = &ts->driver.ipaccess.fd;
+	bsc_unregister_fd(bfd);
+	close(bfd->fd);
+	bfd->fd = -1;
+
+	/* destroy */
+	e1inp_sign_link_destroy(trx->rsl_link);
+	trx->rsl_link = NULL;
+
+	return -1;
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *link;
+	struct msgb *msg;
+	struct ipaccess_head *hh;
+	int ret = 0, error;
+
+	msg = ipaccess_read_msg(bfd, &error);
+	if (!msg) {
+		if (error == 0) {
+			int ret = ipaccess_drop(e1i_ts, bfd);
+			if (ret >= 0)
+				LOGP(DINP, LOGL_NOTICE, "BTS %u disappeared, dead socket\n",
+					ret);
+			else
+				LOGP(DINP, LOGL_NOTICE, "unknown BTS disappeared, dead socket\n");
+		}
+		return error;
+	}
+
+	DEBUGP(DMI, "RX %u: %s\n", ts_nr, hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		ret = ipaccess_rcvmsg(line, msg, bfd);
+		if (ret < 0)
+			ipaccess_drop(e1i_ts, bfd);
+		msgb_free(msg);
+		return ret;
+	}
+	/* BIG FAT WARNING: bfd might no longer exist here, since ipaccess_rcvmsg()
+	 * might have free'd it !!! */
+
+	link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0);
+	if (!link) {
+		LOGP(DINP, LOGL_ERROR, "no matching signalling link for "
+			"hh->proto=0x%02x\n", hh->proto);
+		msgb_free(msg);
+		return -EIO;
+	}
+	msg->trx = link->trx;
+
+	switch (link->type) {
+	case E1INP_SIGN_RSL:
+		if (!(msg->trx->bts->ip_access.flags & (RSL_UP << msg->trx->nr))) {
+			e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+			msg->trx->bts->ip_access.flags |= (RSL_UP << msg->trx->nr);
+		}
+		ret = abis_rsl_rcvmsg(msg);
+		break;
+	case E1INP_SIGN_OML:
+		if (!(msg->trx->bts->ip_access.flags & OML_UP)) {
+			e1inp_event(e1i_ts, S_INP_TEI_UP, link->tei, link->sapi);
+			msg->trx->bts->ip_access.flags |= OML_UP;
+		}
+		ret = abis_nm_rcvmsg(msg);
+		break;
+	default:
+		LOGP(DINP, LOGL_NOTICE, "Unknown IP.access protocol proto=0x%02x\n", hh->proto);
+		msgb_free(msg);
+		break;
+	}
+	return ret;
+}
+
+void ipaccess_prepend_header(struct msgb *msg, int proto)
+{
+	struct ipaccess_head *hh;
+
+	/* prepend the ip.access header */
+	hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh));
+	hh->len = htons(msg->len - sizeof(*hh));
+	hh->proto = proto;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+	e1i_ts->driver.ipaccess.fd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+	struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+	/* trigger write of ts1, due to tx delay timer */
+	ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *sign_link;
+	struct msgb *msg;
+	u_int8_t proto;
+	int ret;
+
+	bfd->when &= ~BSC_FD_WRITE;
+
+	/* get the next msg for this timeslot */
+	msg = e1inp_tx_ts(e1i_ts, &sign_link);
+	if (!msg) {
+		/* no message after tx delay timer */
+		return 0;
+	}
+
+	switch (sign_link->type) {
+	case E1INP_SIGN_OML:
+		proto = IPAC_PROTO_OML;
+		break;
+	case E1INP_SIGN_RSL:
+		proto = IPAC_PROTO_RSL;
+		break;
+	default:
+		msgb_free(msg);
+		bfd->when |= BSC_FD_WRITE; /* come back for more msg */
+		return -EINVAL;
+	}
+
+	msg->l2h = msg->data;
+	ipaccess_prepend_header(msg, sign_link->tei);
+
+	DEBUGP(DMI, "TX %u: %s\n", ts_nr, hexdump(msg->l2h, msgb_l2len(msg)));
+
+	ret = send(bfd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	/* set tx delay timer for next event */
+	e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+	e1i_ts->sign.tx_timer.data = e1i_ts;
+
+	/* Reducing this might break the nanoBTS 900 init. */
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay);
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int ipaccess_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	unsigned int idx = ts_nr-1;
+	struct e1inp_ts *e1i_ts;
+	int rc = 0;
+
+	/* In case of early RSL we might not yet have a line */
+
+	if (line)
+ 		e1i_ts = &line->ts[idx];
+
+	if (!line || e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+		if (what & BSC_FD_READ)
+			rc = handle_ts1_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_ts1_write(bfd);
+	} else
+		LOGP(DINP, LOGL_ERROR, "unknown E1 TS type %u\n", e1i_ts->type);
+
+	return rc;
+}
+
+struct e1inp_driver ipaccess_driver = {
+	.name = "ip.access",
+	.want_write = ts_want_write,
+	.default_delay = 0,
+};
+
+/* callback of the OML listening filedescriptor */
+static int listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	int ret;
+	int idx = 0;
+	int i;
+	struct e1inp_line *line;
+	struct e1inp_ts *e1i_ts;
+	struct bsc_fd *bfd;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (ret < 0) {
+		perror("accept");
+		return ret;
+	}
+	LOGP(DINP, LOGL_NOTICE, "accept()ed new OML link from %s\n",
+		inet_ntoa(sa.sin_addr));
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line) {
+		close(ret);
+		return -ENOMEM;
+	}
+	line->driver = &ipaccess_driver;
+	//line->driver_data = e1h;
+	/* create virrtual E1 timeslots for signalling */
+	e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+
+	/* initialize the fds */
+	for (i = 0; i < ARRAY_SIZE(line->ts); ++i)
+		line->ts[i].driver.ipaccess.fd.fd = -1;
+
+	e1i_ts = &line->ts[idx];
+
+	bfd = &e1i_ts->driver.ipaccess.fd;
+	bfd->fd = ret;
+	bfd->data = line;
+	bfd->priv_nr = PRIV_OML;
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(line);
+		return ret;
+	}
+
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = ipaccess_send_id_req(bfd->fd);
+
+        return ret;
+	//return e1inp_line_register(line);
+}
+
+static int rsl_listen_fd_cb(struct bsc_fd *listen_bfd, unsigned int what)
+{
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+	struct bsc_fd *bfd;
+	int ret;
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	bfd = talloc_zero(tall_bsc_ctx, struct bsc_fd);
+	if (!bfd)
+		return -ENOMEM;
+
+	/* Some BTS has connected to us, but we don't know yet which line
+	 * (as created by the OML link) to associate it with.  Thus, we
+	 * allocate a temporary bfd until we have received ID from BTS */
+
+	bfd->fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (bfd->fd < 0) {
+		perror("accept");
+		return bfd->fd;
+	}
+	LOGP(DINP, LOGL_NOTICE, "accept()ed new RSL link from %s\n", inet_ntoa(sa.sin_addr));
+	bfd->priv_nr = PRIV_RSL;
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ;
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
+		close(bfd->fd);
+		talloc_free(bfd);
+		return ret;
+	}
+	/* Request ID. FIXME: request LOCATION, HW/SW VErsion, Unit Name, Serno */
+	ret = write(bfd->fd, id_req, sizeof(id_req));
+
+	return 0;
+}
+
+/* Actively connect to a BTS.  Currently used by ipaccess-config.c */
+int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
+{
+	struct e1inp_ts *e1i_ts = &line->ts[0];
+	struct bsc_fd *bfd = &e1i_ts->driver.ipaccess.fd;
+	int ret, on = 1;
+
+	bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	bfd->cb = ipaccess_fd_cb;
+	bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+	bfd->data = line;
+	bfd->priv_nr = PRIV_OML;
+
+	if (bfd->fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not create TCP socket.\n");
+		return -EIO;
+	}
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = connect(bfd->fd, (struct sockaddr *) sa, sizeof(*sa));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not connect socket\n");
+		close(bfd->fd);
+		return ret;
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		close(bfd->fd);
+		return ret;
+	}
+	
+	line->driver = &ipaccess_driver;
+
+        return ret;
+	//return e1inp_line_register(line);
+}
+
+int ipaccess_setup(struct gsm_network *gsmnet)
+{
+	int ret;
+
+	/* register the driver with the core */
+	/* FIXME: do this in the plugin initializer function */
+	ret = e1inp_driver_register(&ipaccess_driver);
+	if (ret)
+		return ret;
+
+	e1h = talloc_zero(tall_bsc_ctx, struct ia_e1_handle);
+	if (!e1h)
+		return -ENOMEM;
+
+	e1h->gsmnet = gsmnet;
+
+	/* Listen for OML connections */
+	ret = make_sock(&e1h->listen_fd, IPPROTO_TCP, 0, IPA_TCP_PORT_OML,
+			listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	/* Listen for RSL connections */
+	ret = make_sock(&e1h->rsl_listen_fd, IPPROTO_TCP, 0,
+			IPA_TCP_PORT_RSL, rsl_listen_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
diff --git a/src/libabis/input/lapd.c b/src/libabis/input/lapd.c
new file mode 100644
index 0000000..7bce6cc
--- /dev/null
+++ b/src/libabis/input/lapd.c
@@ -0,0 +1,710 @@
+/* OpenBSC minimal LAPD implementation */
+
+/* (C) 2009 by oystein@homelien.no
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* TODO:
+	* detect RR timeout and set SAP state back to SABM_RETRANSMIT
+	* use of value_string
+	* further code cleanup (spaghetti)
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "lapd.h"
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/msgb.h>
+#include <osmocore/timer.h>
+#include <openbsc/debug.h>
+
+#define SABM_INTERVAL		0, 300000
+
+typedef enum {
+	LAPD_TEI_NONE = 0,
+	LAPD_TEI_ASSIGNED,
+	LAPD_TEI_ACTIVE,
+} lapd_tei_state;
+
+const char *lapd_tei_states[] = {
+	"NONE",
+	"ASSIGNED",
+	"ACTIVE",
+};
+
+typedef enum {
+	LAPD_TYPE_NONE = 0,
+
+	LAPD_TYPE_I,
+	LAPD_TYPE_S,
+	LAPD_TYPE_U,
+} lapd_msg_type;
+
+typedef enum {
+	/* commands/responses */
+	LAPD_CMD_NONE = 0,
+
+	LAPD_CMD_I,
+	LAPD_CMD_RR,
+	LAPD_CMD_RNR,
+	LAPD_CMD_REJ,
+
+	LAPD_CMD_SABME,
+	LAPD_CMD_DM,
+	LAPD_CMD_UI,
+	LAPD_CMD_DISC,
+	LAPD_CMD_UA,
+	LAPD_CMD_FRMR,
+	LAPD_CMD_XID,
+} lapd_cmd_type;
+
+const char *lapd_cmd_types[] = {
+	"NONE",
+
+	"I",
+	"RR",
+	"RNR",
+	"REJ",
+
+	"SABME",
+	"DM",
+	"UI",
+	"DISC",
+	"UA",
+	"FRMR",
+	"XID",
+
+};
+
+enum lapd_sap_state {
+	SAP_STATE_INACTIVE,
+	SAP_STATE_SABM_RETRANS,
+	SAP_STATE_ACTIVE,
+};
+
+const char *lapd_sap_states[] = {
+	"INACTIVE",
+	"SABM_RETRANS",
+	"ACTIVE",
+};
+
+const char *lapd_msg_types = "?ISU";
+
+/* structure representing an allocated TEI within a LAPD instance */
+struct lapd_tei {
+	struct llist_head list;
+	struct lapd_instance *li;
+	uint8_t tei;
+	lapd_tei_state state;
+
+	struct llist_head sap_list;
+};
+
+/* Structure representing a SAP within a TEI. We use this for TE-mode to
+ * re-transmit SABM */
+struct lapd_sap {
+	struct llist_head list;
+	struct lapd_tei *tei;
+	uint8_t sapi;
+	enum lapd_sap_state state;
+
+	/* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */
+	int vs;			/* next to be transmitted */
+	int va;			/* last acked by peer */
+	int vr;			/* next expected to be received */
+
+	struct timer_list sabme_timer;	/* timer to re-transmit SABM message */
+};
+
+/* 3.5.2.2   Send state variable V(S)
+ * Each point-to-point data link connection endpoint shall have an associated V(S) when using I frame
+ * commands. V(S) denotes the sequence number of the next I frame to be transmitted. The V(S) can
+ * take on the value 0 through n minus 1. The value of V(S) shall be incremented by 1 with each
+ * successive I frame transmission, and shall not exceed V(A) by more than the maximum number of
+ * outstanding I frames k. The value of k may be in the range of 1 ≤ k ≤ 127.
+ *
+ * 3.5.2.3   Acknowledge state variable V(A)
+ * Each point-to-point data link connection endpoint shall have an associated V(A) when using I frame
+ * commands and supervisory frame commands/responses. V(A) identifies the last I frame that has been
+ * acknowledged by its peer [V(A) − 1 equals the N(S) of the last acknowledged I frame]. V(A) can
+ * take on the value 0 through n minus 1. The value of V(A) shall be updated by the valid N(R) values
+ * received from its peer (see 3.5.2.6). A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤
+ * V(S).
+ *
+ * 3.5.2.5    Receive state variable V(R)
+ * Each point-to-point data link connection endpoint shall have an associated V(R) when using I frame
+ * commands and supervisory frame commands/responses. V(R) denotes the sequence number of the
+ * next in-sequence I frame expected to be received. V(R) can take on the value 0 through n minus 1.
+ * The value of V(R) shall be incremented by one with the receipt of an error-free, in-sequence I frame
+ * whose N(S) equals V(R).
+ */
+#define	LAPD_NS(sap) (sap->vs)
+#define	LAPD_NR(sap) (sap->vr)
+
+/* 3.5.2.4    Send sequence number N(S)
+ * Only I frames contain N(S), the send sequence number of transmitted I frames. At the time that an in-
+ * sequence I frame is designated for transmission, the value of N(S) is set equal to V(S).
+ *
+ * 3.5.2.6    Receive sequence number N(R)
+ * All I frames and supervisory frames contain N(R), the expected send sequence number of the next
+ * received I frame. At the time that a frame of the above types is designated for transmission, the value
+ * of N(R) is set equal to V(R). N(R) indicates that the data link layer entity transmitting the N(R) has
+ * correctly received all I frames numbered up to and including N(R) − 1.
+ */
+
+/* Resolve TEI structure from given numeric TEI */
+static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei)
+{
+	struct lapd_tei *lt;
+
+	llist_for_each_entry(lt, &li->tei_list, list) {
+		if (lt->tei == tei)
+			return lt;
+	}
+	return NULL;
+};
+
+static void lapd_tei_set_state(struct lapd_tei *teip, int newstate)
+{
+	DEBUGP(DMI, "state change on TEI %d: %s -> %s\n", teip->tei,
+		   lapd_tei_states[teip->state], lapd_tei_states[newstate]);
+	teip->state = newstate;
+};
+
+/* Allocate a new TEI */
+struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei)
+{
+	struct lapd_tei *teip;
+
+	teip = talloc_zero(li, struct lapd_tei);
+	if (!teip)
+		return NULL;
+
+	teip->li = li;
+	teip->tei = tei;
+	llist_add(&teip->list, &li->tei_list);
+	INIT_LLIST_HEAD(&teip->sap_list);
+
+	lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
+
+	return teip;
+}
+
+/* Find a SAP within a given TEI */
+static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi)
+{
+	struct lapd_sap *sap;
+
+	llist_for_each_entry(sap, &teip->sap_list, list) {
+		if (sap->sapi == sapi)
+			return sap;
+	}
+
+	return NULL;
+}
+
+static void sabme_timer_cb(void *_sap);
+
+/* Allocate a new SAP within a given TEI */
+static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi)
+{
+	struct lapd_sap *sap = talloc_zero(teip, struct lapd_sap);
+
+	LOGP(DMI, LOGL_INFO, "Allocating SAP for SAPI=%u / TEI=%u\n",
+		sapi, teip->tei);
+
+	sap->sapi = sapi;
+	sap->tei = teip;
+	sap->sabme_timer.cb = &sabme_timer_cb;
+	sap->sabme_timer.data = sap;
+
+	llist_add(&sap->list, &teip->sap_list);
+
+	return sap;
+}
+
+static void lapd_sap_set_state(struct lapd_tei *teip, uint8_t sapi,
+				enum lapd_sap_state newstate)
+{
+	struct lapd_sap *sap = lapd_sap_find(teip, sapi);
+	if (!sap)
+		return;
+
+	DEBUGP(DMI, "state change on TEI %u / SAPI %u: %s -> %s\n", teip->tei,
+		sapi, lapd_sap_states[sap->state], lapd_sap_states[newstate]);
+	switch (sap->state) {
+	case SAP_STATE_SABM_RETRANS:
+		if (newstate != SAP_STATE_SABM_RETRANS)
+			bsc_del_timer(&sap->sabme_timer);
+		break;
+	default:
+		if (newstate == SAP_STATE_SABM_RETRANS)
+			bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
+		break;
+	}
+
+	sap->state = newstate;
+};
+
+/* Input function into TEI manager */
+static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len)
+{
+	uint8_t entity = data[0];
+	uint8_t ref = data[1];
+	uint8_t mt = data[3];
+	uint8_t action = data[4] >> 1;
+	uint8_t e = data[4] & 1;
+	uint8_t resp[8];
+	struct lapd_tei *teip;
+
+	DEBUGP(DMI, "TEIMGR: entity %x, ref %x, mt %x, action %x, e %x\n", entity, ref, mt, action, e);
+
+	switch (mt) {
+	case 0x01:	/* IDENTITY REQUEST */
+		DEBUGP(DMI, "TEIMGR: identity request for TEI %u\n", action);
+
+		teip = teip_from_tei(li, action);
+		if (!teip) {
+			LOGP(DMI, LOGL_INFO, "TEI MGR: New TEI %u\n", action);
+			lapd_tei_alloc(li, action);
+		}
+
+		/* Send ACCEPT */
+		memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8);
+		resp[7] = (action << 1) | 1;
+		li->transmit_cb(resp, 8, li->cbdata);
+
+		if (teip->state == LAPD_TEI_NONE)
+			lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
+		break;
+	default:
+		LOGP(DMI, LOGL_NOTICE, "TEIMGR: unknown mt %x action %x\n",
+		     mt, action);
+		break;
+	};
+};
+
+/* General input function for any data received for this LAPD instance */
+uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len,
+		      int *ilen, lapd_mph_type *prim)
+{
+	uint8_t sapi, cr, tei, command;
+	int pf, ns, nr;
+	uint8_t *contents;
+	struct lapd_tei *teip;
+	struct lapd_sap *sap;
+
+	uint8_t resp[8];
+	int l = 0;
+
+	*ilen = 0;
+	*prim = 0;
+
+	if (len < 2) {
+		DEBUGP(DMI, "len %d < 2\n", len);
+		return NULL;
+	};
+
+	if ((data[0] & 1) != 0 || (data[1] & 1) != 1) {
+		DEBUGP(DMI, "address field %x/%x not well formed\n", data[0],
+			   data[1]);
+		return NULL;
+	};
+
+	sapi = data[0] >> 2;
+	cr = (data[0] >> 1) & 1;
+	tei = data[1] >> 1;
+	command = li->network_side ^ cr;
+	//DEBUGP(DMI, "  address sapi %x tei %d cmd %d cr %d\n", sapi, tei, command, cr);
+
+	if (len < 3) {
+		DEBUGP(DMI, "len %d < 3\n", len);
+		return NULL;
+	};
+
+	lapd_msg_type typ = 0;
+	lapd_cmd_type cmd = 0;
+	pf = -1;
+	ns = -1;
+	nr = -1;
+	if ((data[2] & 1) == 0) {
+		typ = LAPD_TYPE_I;
+		assert(len >= 4);
+		ns = data[2] >> 1;
+		nr = data[3] >> 1;
+		pf = data[3] & 1;
+		cmd = LAPD_CMD_I;
+	} else if ((data[2] & 3) == 1) {
+		typ = LAPD_TYPE_S;
+		assert(len >= 4);
+		nr = data[3] >> 1;
+		pf = data[3] & 1;
+		switch (data[2]) {
+		case 0x1:
+			cmd = LAPD_CMD_RR;
+			break;
+		case 0x5:
+			cmd = LAPD_CMD_RNR;
+			break;
+		case 0x9:
+			cmd = LAPD_CMD_REJ;
+			break;
+		default:
+			LOGP(DMI, LOGL_ERROR, "unknown LAPD S cmd %x\n", data[2]);
+			return NULL;
+		};
+	} else if ((data[2] & 3) == 3) {
+		typ = LAPD_TYPE_U;
+		pf = (data[2] >> 4) & 1;
+		int val = data[2] & ~(1 << 4);
+		switch (val) {
+		case 0x6f:
+			cmd = LAPD_CMD_SABME;
+			break;
+		case 0x0f:
+			cmd = LAPD_CMD_DM;
+			break;
+		case 0x03:
+			cmd = LAPD_CMD_UI;
+			break;
+		case 0x43:
+			cmd = LAPD_CMD_DISC;
+			break;
+		case 0x63:
+			cmd = LAPD_CMD_UA;
+			break;
+		case 0x87:
+			cmd = LAPD_CMD_FRMR;
+			break;
+		case 0xaf:
+			cmd = LAPD_CMD_XID;
+			break;
+
+		default:
+			LOGP(DMI, LOGL_ERROR, "unknown U cmd %x "
+			     "(pf %x data %x)\n", val, pf, data[2]);
+			return NULL;
+		};
+	};
+
+	contents = &data[4];
+	if (typ == LAPD_TYPE_U)
+		contents--;
+	*ilen = len - (contents - data);
+
+	if (tei == 127)
+		lapd_tei_receive(li, contents, *ilen);
+
+	teip = teip_from_tei(li, tei);
+	if (!teip) {
+		LOGP(DMI, LOGL_NOTICE, "Unknown TEI %u\n", tei);
+		return NULL;
+	}
+
+	sap = lapd_sap_find(teip, sapi);
+	if (!sap) {
+		LOGP(DMI, LOGL_INFO, "No SAP for TEI=%u / SAPI=%u, "
+			"allocating\n", tei, sapi);
+		sap = lapd_sap_alloc(teip, sapi);
+	}
+
+	DEBUGP(DMI, "<- %c %s sapi %x tei %3d cmd %x pf %x ns %3d nr %3d "
+	     "ilen %d teip %p vs %d va %d vr %d len %d\n",
+	     lapd_msg_types[typ], lapd_cmd_types[cmd], sapi, tei, command, pf,
+	     ns, nr, *ilen, teip, sap->vs, sap->va, sap->vr, len);
+
+	switch (cmd) {
+	case LAPD_CMD_I:
+		if (ns != sap->vr) {
+			DEBUGP(DMI, "ns %d != vr %d\n", ns, sap->vr);
+			if (ns == ((sap->vr - 1) & 0x7f)) {
+				DEBUGP(DMI, "DOUBLE FRAME, ignoring\n");
+				cmd = 0;	// ignore
+			} else {
+				assert(0);
+			};
+		} else {
+			//printf("IN SEQUENCE\n");
+			sap->vr = (ns + 1) & 0x7f;	// FIXME: hack!
+		};
+
+		break;
+	case LAPD_CMD_UI:
+		break;
+	case LAPD_CMD_SABME:
+		sap->vs = 0;
+		sap->vr = 0;
+		sap->va = 0;
+
+		// ua
+		resp[l++] = data[0];
+		resp[l++] = (tei << 1) | 1;
+		resp[l++] = 0x73;
+		li->transmit_cb(resp, l, li->cbdata);
+		if (teip->state != LAPD_TEI_ACTIVE) {
+			if (teip->state == LAPD_TEI_ASSIGNED) {
+				lapd_tei_set_state(teip,
+						   LAPD_TEI_ACTIVE);
+				//printf("ASSIGNED and ACTIVE\n");
+			} else {
+#if 0
+				DEBUGP(DMI, "rr in strange state, send rej\n");
+
+				// rej
+				resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2);
+				resp[l++] = (tei << 1) | 1;
+				resp[l++] = 0x09;	//rej
+				resp[l++] = ((sap->vr + 1) << 1) | 0;
+				li->transmit_cb(resp, l, li->cbdata);
+				pf = 0;	// dont reply
+#endif
+			};
+		};
+
+		*prim = LAPD_MPH_ACTIVATE_IND;
+		break;
+	case LAPD_CMD_UA:
+		sap->vs = 0;
+		sap->vr = 0;
+		sap->va = 0;
+		lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
+		lapd_sap_set_state(teip, sapi, SAP_STATE_ACTIVE);
+		*prim = LAPD_MPH_ACTIVATE_IND;
+		break;
+	case LAPD_CMD_RR:
+		sap->va = (nr & 0x7f);
+#if 0
+		if (teip->state != LAPD_TEI_ACTIVE) {
+			if (teip->state == LAPD_TEI_ASSIGNED) {
+				lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
+				*prim = LAPD_MPH_ACTIVATE_IND;
+				//printf("ASSIGNED and ACTIVE\n");
+			} else {
+#if 0
+				DEBUGP(DMI, "rr in strange " "state, send rej\n");
+
+				// rej
+				resp[l++] = (sap-> sapi << 2) | (li->network_side ? 0 : 2);
+				resp[l++] = (tei << 1) | 1;
+				resp[l++] = 0x09;	//rej
+				resp[l++] =
+				    ((sap->vr + 1) << 1) | 0;
+				li->transmit_cb(resp, l, li->cbdata);
+				pf = 0;	// dont reply
+#endif
+			};
+		};
+#endif
+		if (pf) {
+			// interrogating us, send rr
+			resp[l++] = data[0];
+			resp[l++] = (tei << 1) | 1;
+			resp[l++] = 0x01;	// rr
+			resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1);	// pf bit from req
+
+			li->transmit_cb(resp, l, li->cbdata);
+
+		};
+		break;
+	case LAPD_CMD_FRMR:
+		// frame reject
+#if 0
+		if (teip->state == LAPD_TEI_ACTIVE)
+			*prim = LAPD_MPH_DEACTIVATE_IND;
+		lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
+#endif
+		LOGP(DMI, LOGL_NOTICE, "frame reject, ignoring\n");
+		break;
+	case LAPD_CMD_DISC:
+		// disconnect
+		resp[l++] = data[0];
+		resp[l++] = (tei << 1) | 1;
+		resp[l++] = 0x73;
+		li->transmit_cb(resp, l, li->cbdata);
+		lapd_tei_set_state(teip, LAPD_TEI_NONE);
+		break;
+	default:
+		LOGP(DMI, LOGL_NOTICE, "unknown cmd for tei %d (cmd %x)\n",
+		     tei, cmd);
+		break;
+	}
+
+	if (typ == LAPD_TYPE_I) {
+		/* send rr
+		 * Thu Jan 22 19:17:13 2009 <4000> sangoma.c:340 read  (62/25)   4: fa 33 01 0a 
+		 * lapd <- S RR sapi 3e tei  25 cmd 0 pf 0 ns  -1 nr   5 ilen 0 teip 0x613800 vs 7 va 5 vr 2 len 4
+		 */
+
+		/* interrogating us, send rr */
+		DEBUGP(DMI, "Sending RR response\n");
+		resp[l++] = data[0];
+		resp[l++] = (tei << 1) | 1;
+		resp[l++] = 0x01;	// rr
+		resp[l++] = (LAPD_NR(sap) << 1) | (data[3] & 1);	// pf bit from req
+
+		li->transmit_cb(resp, l, li->cbdata);
+
+		if (cmd != 0) {
+			*prim = LAPD_DL_DATA_IND;
+			return contents;
+		}
+	} else if (tei != 127 && typ == LAPD_TYPE_U && cmd == LAPD_CMD_UI) {
+		*prim = LAPD_DL_UNITDATA_IND;
+		return contents;
+	}
+
+	return NULL;
+};
+
+/* low-level function to send a single SABM message */
+static int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+	struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM");
+	if (!msg)
+		return -ENOMEM;
+
+	DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi);
+
+	msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0));
+	msgb_put_u8(msg, (tei << 1) | 1);
+	msgb_put_u8(msg, 0x7F);
+
+	li->transmit_cb(msg->data, msg->len, li->cbdata);
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+/* timer call-back function for SABM re-transmission */
+static void sabme_timer_cb(void *_sap)
+{
+	struct lapd_sap *sap = _sap;
+
+	lapd_send_sabm(sap->tei->li, sap->tei->tei, sap->sapi);
+
+	if (sap->state == SAP_STATE_SABM_RETRANS)
+		bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
+}
+
+/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+	struct lapd_sap *sap;
+	struct lapd_tei *teip;
+
+	teip = teip_from_tei(li, tei);
+	if (!teip)
+		teip = lapd_tei_alloc(li, tei);
+
+	sap = lapd_sap_find(teip, sapi);
+	if (sap)
+		return -EEXIST;
+
+	sap = lapd_sap_alloc(teip, sapi);
+
+	lapd_sap_set_state(teip, sapi, SAP_STATE_SABM_RETRANS);
+
+	return 0;
+}
+
+/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+	struct lapd_tei *teip;
+	struct lapd_sap *sap;
+
+	teip = teip_from_tei(li, tei);
+	if (!teip)
+		return -ENODEV;
+
+	sap = lapd_sap_find(teip, sapi);
+	if (!sap)
+		return -ENODEV;
+
+	lapd_sap_set_state(teip, sapi, SAP_STATE_INACTIVE);
+
+	llist_del(&sap->list);
+	talloc_free(sap);
+
+	return 0;
+}
+
+/* Transmit Data (I-Frame) on the given LAPD Instance / TEI / SAPI */
+void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
+		   uint8_t *data, unsigned int len)
+{
+	struct lapd_tei *teip = teip_from_tei(li, tei);
+	struct lapd_sap *sap;
+
+	if (!teip) {
+		LOGP(DMI, LOGL_ERROR, "Cannot transmit on non-existing "
+		     "TEI %u\n", tei);
+		return;
+	}
+
+	sap = lapd_sap_find(teip, sapi);
+	if (!sap) {
+		LOGP(DMI, LOGL_INFO, "Tx on unknown SAPI=%u in TEI=%u, "
+			"allocating\n", sapi, tei);
+		sap = lapd_sap_alloc(teip, sapi);
+	}
+
+	/* prepend stuff */
+	uint8_t buf[10000];
+	memset(buf, 0, sizeof(buf));
+	memmove(buf + 4, data, len);
+	len += 4;
+
+	buf[0] = (sapi << 2) | (li->network_side ? 2 : 0);
+	buf[1] = (tei << 1) | 1;
+	buf[2] = (LAPD_NS(sap) << 1);
+	buf[3] = (LAPD_NR(sap) << 1) | 0;
+
+	sap->vs = (sap->vs + 1) & 0x7f;
+
+	li->transmit_cb(buf, len, li->cbdata);
+};
+
+/* Allocate a new LAPD instance */
+struct lapd_instance *lapd_instance_alloc(int network_side,
+					  void (*tx_cb)(uint8_t *data, int len,
+							void *cbdata), void *cbdata)
+{
+	struct lapd_instance *li;
+
+	li = talloc_zero(NULL, struct lapd_instance);
+	if (!li)
+		return NULL;
+
+	li->transmit_cb = tx_cb;
+	li->cbdata = cbdata;
+	li->network_side = network_side;
+	INIT_LLIST_HEAD(&li->tei_list);
+
+	return li;
+}
diff --git a/src/libabis/input/lapd.h b/src/libabis/input/lapd.h
new file mode 100644
index 0000000..fd11eda
--- /dev/null
+++ b/src/libabis/input/lapd.h
@@ -0,0 +1,46 @@
+#ifndef OPENBSC_LAPD_H
+#define OPENBSC_LAPD_H
+
+#include <stdint.h>
+
+#include <osmocore/linuxlist.h>
+
+typedef enum {
+	LAPD_MPH_NONE	= 0,
+
+	LAPD_MPH_ACTIVATE_IND,
+	LAPD_MPH_DEACTIVATE_IND,
+
+	LAPD_DL_DATA_IND,
+	LAPD_DL_UNITDATA_IND,
+
+} lapd_mph_type;
+
+struct lapd_instance {
+	struct llist_head list;		/* list of LAPD instances */
+	int network_side;
+
+	void (*transmit_cb)(uint8_t *data, int len, void *cbdata);
+	void *cbdata;
+
+	struct llist_head tei_list;	/* list of TEI in this LAPD instance */
+};
+
+extern uint8_t *lapd_receive(struct lapd_instance *li, uint8_t *data, unsigned int len,
+			     int *ilen, lapd_mph_type *prim);
+
+extern void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
+			  uint8_t *data, unsigned int len);
+
+struct lapd_instance *lapd_instance_alloc(int network_side,
+					  void (*tx_cb)(uint8_t *data, int len,
+							void *cbdata), void *cbdata);
+
+
+/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
+
+/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
+
+#endif /* OPENBSC_LAPD_H */
diff --git a/src/libabis/input/misdn.c b/src/libabis/input/misdn.c
new file mode 100644
index 0000000..4598879
--- /dev/null
+++ b/src/libabis/input/misdn.c
@@ -0,0 +1,542 @@
+/* OpenBSC Abis input driver for mISDNuser */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <mISDNif.h>
+
+//#define AF_COMPATIBILITY_FUNC
+//#include <compat_af_isdn.h>
+#ifndef AF_ISDN
+#define AF_ISDN 34
+#define PF_ISDN AF_ISDN
+#endif
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+
+#define TS1_ALLOC_SIZE	300
+
+struct prim_name {
+	unsigned int prim;
+	const char *name;
+};
+
+const struct prim_name prim_names[] = {
+	{ PH_CONTROL_IND, "PH_CONTROL_IND" },
+	{ PH_DATA_IND, "PH_DATA_IND" },
+	{ PH_DATA_CNF, "PH_DATA_CNF" },
+	{ PH_ACTIVATE_IND, "PH_ACTIVATE_IND" },
+	{ DL_ESTABLISH_IND, "DL_ESTABLISH_IND" },
+	{ DL_ESTABLISH_CNF, "DL_ESTABLISH_CNF" },
+	{ DL_RELEASE_IND, "DL_RELEASE_IND" },
+	{ DL_RELEASE_CNF, "DL_RELEASE_CNF" },
+	{ DL_DATA_IND, "DL_DATA_IND" },
+	{ DL_UNITDATA_IND, "DL_UNITDATA_IND" },
+	{ DL_INFORMATION_IND, "DL_INFORMATION_IND" },
+	{ MPH_ACTIVATE_IND, "MPH_ACTIVATE_IND" },
+	{ MPH_DEACTIVATE_IND, "MPH_DEACTIVATE_IND" },
+};
+
+const char *get_prim_name(unsigned int prim)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(prim_names); i++) {
+		if (prim_names[i].prim == prim)
+			return prim_names[i].name;
+	}
+
+	return "UNKNOWN";
+}
+
+static int handle_ts1_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *link;
+	struct msgb *msg = msgb_alloc(TS1_ALLOC_SIZE, "mISDN TS1");
+	struct sockaddr_mISDN l2addr;
+	struct mISDNhead *hh;
+	socklen_t alen;
+	int ret;
+
+	if (!msg)
+		return -ENOMEM;
+
+	hh = (struct mISDNhead *) msg->data;
+
+	alen = sizeof(l2addr);
+	ret = recvfrom(bfd->fd, msg->data, 300, 0,
+		       (struct sockaddr *) &l2addr, &alen);
+	if (ret < 0) {
+		fprintf(stderr, "recvfrom error  %s\n", strerror(errno));
+		return ret;
+	}
+
+	if (alen != sizeof(l2addr)) {
+		fprintf(stderr, "%s error len\n", __func__);
+		return -EINVAL;
+	}
+
+	msgb_put(msg, ret);
+
+	DEBUGP(DMI, "alen =%d, dev(%d) channel(%d) sapi(%d) tei(%d)\n",
+		alen, l2addr.dev, l2addr.channel, l2addr.sapi, l2addr.tei);
+
+	DEBUGP(DMI, "<= len = %d, prim(0x%x) id(0x%x): %s\n",
+		ret, hh->prim, hh->id, get_prim_name(hh->prim));
+
+	switch (hh->prim) {
+	case DL_INFORMATION_IND:
+		/* mISDN tells us which channel number is allocated for this
+		 * tuple of (SAPI, TEI). */
+		DEBUGP(DMI, "DL_INFORMATION_IND: use channel(%d) sapi(%d) tei(%d) for now\n",
+			l2addr.channel, l2addr.sapi, l2addr.tei);
+		link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi);
+		if (!link) {
+			DEBUGPC(DMI, "mISDN message for unknown sign_link\n");
+			msgb_free(msg);
+			return -EINVAL;
+		}
+		/* save the channel number in the driver private struct */
+		link->driver.misdn.channel = l2addr.channel;
+		break;
+	case DL_ESTABLISH_IND:
+		DEBUGP(DMI, "DL_ESTABLISH_IND: channel(%d) sapi(%d) tei(%d)\n",
+			l2addr.channel, l2addr.sapi, l2addr.tei);
+		/* For some strange reason, sometimes the DL_INFORMATION_IND tells
+		 * us the wrong channel, and we only get the real channel number
+		 * during the DL_ESTABLISH_IND */
+		link = e1inp_lookup_sign_link(e1i_ts, l2addr.tei, l2addr.sapi);
+		if (!link) {
+			DEBUGPC(DMI, "mISDN message for unknown sign_link\n");
+			msgb_free(msg);
+			return -EINVAL;
+		}
+		/* save the channel number in the driver private struct */
+		link->driver.misdn.channel = l2addr.channel;
+		ret = e1inp_event(e1i_ts, S_INP_TEI_UP, l2addr.tei, l2addr.sapi);
+		break;
+	case DL_RELEASE_IND:
+		DEBUGP(DMI, "DL_RELEASE_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		ret = e1inp_event(e1i_ts, S_INP_TEI_DN, l2addr.tei, l2addr.sapi);
+		break;
+	case DL_DATA_IND:
+	case DL_UNITDATA_IND:
+		msg->l2h = msg->data + MISDN_HEADER_LEN;
+		DEBUGP(DMI, "RX: %s\n", hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+		ret = e1inp_rx_ts(e1i_ts, msg, l2addr.tei, l2addr.sapi);
+		break;
+	case PH_ACTIVATE_IND:
+		DEBUGP(DMI, "PH_ACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		break;
+	case PH_DEACTIVATE_IND:
+		DEBUGP(DMI, "PH_DEACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
+		l2addr.channel, l2addr.sapi, l2addr.tei);
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static int ts_want_write(struct e1inp_ts *e1i_ts)
+{
+	/* We never include the mISDN B-Channel FD into the
+	 * writeset, since it doesn't support poll() based
+	 * write flow control */		
+	if (e1i_ts->type == E1INP_TS_TYPE_TRAU)
+		return 0;
+
+	e1i_ts->driver.misdn.fd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+static void timeout_ts1_write(void *data)
+{
+	struct e1inp_ts *e1i_ts = (struct e1inp_ts *)data;
+
+	/* trigger write of ts1, due to tx delay timer */
+	ts_want_write(e1i_ts);
+}
+
+static int handle_ts1_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct e1inp_sign_link *sign_link;
+	struct sockaddr_mISDN sa;
+	struct msgb *msg;
+	struct mISDNhead *hh;
+	u_int8_t *l2_data;
+	int ret;
+
+	bfd->when &= ~BSC_FD_WRITE;
+
+	/* get the next msg for this timeslot */
+	msg = e1inp_tx_ts(e1i_ts, &sign_link);
+	if (!msg) {
+		/* no message after tx delay timer */
+		return 0;
+	}
+
+	l2_data = msg->data;
+
+	/* prepend the mISDNhead */
+	hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh));
+	hh->prim = DL_DATA_REQ;
+
+	DEBUGP(DMI, "TX channel(%d) TEI(%d) SAPI(%d): %s\n",
+		sign_link->driver.misdn.channel, sign_link->tei,
+		sign_link->sapi, hexdump(l2_data, msg->len - MISDN_HEADER_LEN));
+
+	/* construct the sockaddr */
+	sa.family = AF_ISDN;
+	sa.sapi = sign_link->sapi;
+	sa.dev = sign_link->tei;
+	sa.channel = sign_link->driver.misdn.channel;
+
+	ret = sendto(bfd->fd, msg->data, msg->len, 0,
+		     (struct sockaddr *)&sa, sizeof(sa));
+	if (ret < 0)
+		fprintf(stderr, "%s sendto failed %d\n", __func__, ret);
+	msgb_free(msg);
+
+	/* set tx delay timer for next event */
+	e1i_ts->sign.tx_timer.cb = timeout_ts1_write;
+	e1i_ts->sign.tx_timer.data = e1i_ts;
+	bsc_schedule_timer(&e1i_ts->sign.tx_timer, 0, e1i_ts->sign.delay);
+
+	return ret;
+}
+
+#define BCHAN_TX_GRAN	160
+/* write to a B channel TS */
+static int handle_tsX_write(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct mISDNhead *hh;
+	u_int8_t tx_buf[BCHAN_TX_GRAN + sizeof(*hh)];
+	struct subch_mux *mx = &e1i_ts->trau.mux;
+	int ret;
+
+	hh = (struct mISDNhead *) tx_buf;
+	hh->prim = PH_DATA_REQ;
+
+	subchan_mux_out(mx, tx_buf+sizeof(*hh), BCHAN_TX_GRAN);
+
+	DEBUGP(DMIB, "BCHAN TX: %s\n",
+		hexdump(tx_buf+sizeof(*hh), BCHAN_TX_GRAN));
+
+	ret = send(bfd->fd, tx_buf, sizeof(*hh) + BCHAN_TX_GRAN, 0);
+	if (ret < sizeof(*hh) + BCHAN_TX_GRAN)
+		DEBUGP(DMIB, "send returns %d instead of %zu\n", ret,
+			sizeof(*hh) + BCHAN_TX_GRAN);
+
+	return ret;
+}
+
+#define TSX_ALLOC_SIZE 4096
+/* FIXME: read from a B channel TS */
+static int handle_tsX_read(struct bsc_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct msgb *msg = msgb_alloc(TSX_ALLOC_SIZE, "mISDN TSx");
+	struct mISDNhead *hh;
+	int ret;
+
+	if (!msg)
+		return -ENOMEM;
+
+	hh = (struct mISDNhead *) msg->data;
+
+	ret = recv(bfd->fd, msg->data, TSX_ALLOC_SIZE, 0);
+	if (ret < 0) {
+		fprintf(stderr, "recvfrom error  %s\n", strerror(errno));
+		return ret;
+	}
+
+	msgb_put(msg, ret);
+
+	if (hh->prim != PH_CONTROL_IND)
+		DEBUGP(DMIB, "<= BCHAN len = %d, prim(0x%x) id(0x%x): %s\n",
+			ret, hh->prim, hh->id, get_prim_name(hh->prim));
+
+	switch (hh->prim) {
+	case PH_DATA_IND:
+		msg->l2h = msg->data + MISDN_HEADER_LEN;
+		DEBUGP(DMIB, "BCHAN RX: %s\n",
+			hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+		ret = e1inp_rx_ts(e1i_ts, msg, 0, 0);
+		break;
+	case PH_ACTIVATE_IND:
+	case PH_DATA_CNF:
+		/* physical layer indicates that data has been sent,
+		 * we thus can send some more data */
+		ret = handle_tsX_write(bfd);
+	default:
+		break;
+	}
+	/* FIXME: why do we free signalling msgs in the caller, and trau not? */
+	msgb_free(msg);
+
+	return ret;
+}
+
+/* callback from select.c in case one of the fd's can be read/written */
+static int misdn_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	unsigned int idx = ts_nr-1;
+	struct e1inp_ts *e1i_ts = &line->ts[idx];
+	int rc = 0;
+
+	switch (e1i_ts->type) {
+	case E1INP_TS_TYPE_SIGN:
+		if (what & BSC_FD_READ)
+			rc = handle_ts1_read(bfd);
+		if (what & BSC_FD_WRITE)
+			rc = handle_ts1_write(bfd);
+		break;
+	case E1INP_TS_TYPE_TRAU:
+		if (what & BSC_FD_READ)
+			rc = handle_tsX_read(bfd);
+		/* We never include the mISDN B-Channel FD into the
+		 * writeset, since it doesn't support poll() based
+		 * write flow control */		
+		break;
+	default:
+		fprintf(stderr, "unknown E1 TS type %u\n", e1i_ts->type);
+		break;
+	}
+
+	return rc;
+}
+
+static int activate_bchan(struct e1inp_line *line, int ts, int act)
+{
+	struct mISDNhead hh;
+	int ret;
+	unsigned int idx = ts-1;
+	struct e1inp_ts *e1i_ts = &line->ts[idx];
+	struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd;
+
+	fprintf(stdout, "activate bchan\n");
+	if (act)
+		hh.prim = PH_ACTIVATE_REQ;
+	else
+		hh.prim = PH_DEACTIVATE_REQ;
+
+	hh.id = MISDN_ID_ANY;
+	ret = sendto(bfd->fd, &hh, sizeof(hh), 0, NULL, 0);
+	if (ret < 0) {
+		fprintf(stdout, "could not send ACTIVATE_RQ %s\n",
+			strerror(errno));
+	}
+
+	return ret;
+}
+
+static int mi_e1_line_update(struct e1inp_line *line);
+
+struct e1inp_driver misdn_driver = {
+	.name = "misdn",
+	.want_write = ts_want_write,
+	.default_delay = 50000,
+	.line_update = &mi_e1_line_update,
+};
+
+static int mi_e1_setup(struct e1inp_line *line, int release_l2)
+{
+	int ts, ret;
+
+	/* TS0 is CRC4, don't need any fd for it */
+	for (ts = 1; ts < NUM_E1_TS; ts++) {
+		unsigned int idx = ts-1;
+		struct e1inp_ts *e1i_ts = &line->ts[idx];
+		struct bsc_fd *bfd = &e1i_ts->driver.misdn.fd;
+		struct sockaddr_mISDN addr;
+
+		bfd->data = line;
+		bfd->priv_nr = ts;
+		bfd->cb = misdn_fd_cb;
+
+		switch (e1i_ts->type) {
+		case E1INP_TS_TYPE_NONE:
+			continue;
+			break;
+		case E1INP_TS_TYPE_SIGN:
+			bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT);
+			bfd->when = BSC_FD_READ;
+			break;
+		case E1INP_TS_TYPE_TRAU:
+			bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_RAW);
+			/* We never include the mISDN B-Channel FD into the
+	 		* writeset, since it doesn't support poll() based
+	 		* write flow control */		
+			bfd->when = BSC_FD_READ;
+			break;
+		}
+
+		if (bfd->fd < 0) {
+			fprintf(stderr, "%s could not open socket %s\n",
+				__func__, strerror(errno));
+			return bfd->fd;
+		}
+
+		memset(&addr, 0, sizeof(addr));
+		addr.family = AF_ISDN;
+		addr.dev = line->num;
+		switch (e1i_ts->type) {
+		case E1INP_TS_TYPE_SIGN:
+			addr.channel = 0;
+			/* SAPI not supported yet in kernel */
+			//addr.sapi = e1inp_ts->sign.sapi;
+			addr.sapi = 0;
+			addr.tei = GROUP_TEI;
+			break;
+		case E1INP_TS_TYPE_TRAU:
+			addr.channel = ts;
+			break;
+		default:
+			DEBUGP(DMI, "unsupported E1 TS type: %u\n",
+				e1i_ts->type);
+			break;
+		}
+
+		ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+		if (ret < 0) {
+			fprintf(stderr, "could not bind l2 socket %s\n",
+				strerror(errno));
+			return -EIO;
+		}
+
+		if (e1i_ts->type == E1INP_TS_TYPE_SIGN) {
+			ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2);
+			if (ret < 0) {
+				fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno));
+				return -EIO;
+			}
+		}
+
+		/* FIXME: only activate B-Channels once we start to
+		 * use them to conserve CPU power */
+		if (e1i_ts->type == E1INP_TS_TYPE_TRAU)
+			activate_bchan(line, ts, 1);
+
+		ret = bsc_register_fd(bfd);
+		if (ret < 0) {
+			fprintf(stderr, "could not register FD: %s\n",
+				strerror(ret));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int mi_e1_line_update(struct e1inp_line *line)
+{
+	struct mISDN_devinfo devinfo;
+	int sk, ret, cnt;
+
+	if (line->driver != &misdn_driver)
+		return -EINVAL;
+
+	/* open the ISDN card device */
+	sk = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (sk < 0) {
+		fprintf(stderr, "%s could not open socket %s\n",
+			__func__, strerror(errno));
+		return sk;
+	}
+
+	ret = ioctl(sk, IMGETCOUNT, &cnt);
+	if (ret) {
+		fprintf(stderr, "%s error getting interf count: %s\n",
+			__func__, strerror(errno));
+		close(sk);
+		return -ENODEV;
+	}
+	//DEBUGP(DMI,"%d device%s found\n", cnt, (cnt==1)?"":"s");
+	printf("%d device%s found\n", cnt, (cnt==1)?"":"s");
+#if 1
+	devinfo.id = line->num;
+	ret = ioctl(sk, IMGETDEVINFO, &devinfo);
+	if (ret < 0) {
+		fprintf(stdout, "error getting info for device %d: %s\n",
+			line->num, strerror(errno));
+		return -ENODEV;
+	}
+	fprintf(stdout, "        id:             %d\n", devinfo.id);
+	fprintf(stdout, "        Dprotocols:     %08x\n", devinfo.Dprotocols);
+	fprintf(stdout, "        Bprotocols:     %08x\n", devinfo.Bprotocols);
+	fprintf(stdout, "        protocol:       %d\n", devinfo.protocol);
+	fprintf(stdout, "        nrbchan:        %d\n", devinfo.nrbchan);
+	fprintf(stdout, "        name:           %s\n", devinfo.name);
+#endif
+
+	if (!(devinfo.Dprotocols & (1 << ISDN_P_NT_E1))) {
+		fprintf(stderr, "error: card is not of type E1 (NT-mode)\n");
+		return -EINVAL;
+	}
+
+	ret = mi_e1_setup(line, 1);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+void e1inp_misdn_init(void)
+{
+	/* register the driver with the core */
+	e1inp_driver_register(&misdn_driver);
+}
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
new file mode 100644
index 0000000..de76929
--- /dev/null
+++ b/src/libbsc/Makefile.am
@@ -0,0 +1,25 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libbsc.a
+
+libbsc_a_SOURCES =	abis_nm.c abis_nm_vty.c \
+			abis_om2000.c abis_om2000_vty.c \
+			abis_rsl.c bsc_rll.c \
+			paging.c \
+			bts_ericsson_rbs2000.c \
+			bts_ipaccess_nanobts.c \
+			bts_siemens_bs11.c \
+			bts_hsl_femtocell.c \
+			bts_unknown.c \
+			chan_alloc.c \
+			gsm_subscriber_base.c \
+			handover_decision.c handover_logic.c meas_rep.c \
+			rest_octets.c system_information.c \
+			e1_config.c \
+			transaction.c \
+			bsc_api.c bsc_msc.c bsc_vty.c \
+			gsm_04_08_utils.c \
+			bsc_init.c
+
diff --git a/src/libbsc/Makefile.in b/src/libbsc/Makefile.in
new file mode 100644
index 0000000..e5d3d20
--- /dev/null
+++ b/src/libbsc/Makefile.in
@@ -0,0 +1,504 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libbsc
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libbsc_a_AR = $(AR) $(ARFLAGS)
+libbsc_a_LIBADD =
+am_libbsc_a_OBJECTS = abis_nm.$(OBJEXT) abis_nm_vty.$(OBJEXT) \
+	abis_om2000.$(OBJEXT) abis_om2000_vty.$(OBJEXT) \
+	abis_rsl.$(OBJEXT) bsc_rll.$(OBJEXT) paging.$(OBJEXT) \
+	bts_ericsson_rbs2000.$(OBJEXT) bts_ipaccess_nanobts.$(OBJEXT) \
+	bts_siemens_bs11.$(OBJEXT) bts_hsl_femtocell.$(OBJEXT) \
+	bts_unknown.$(OBJEXT) chan_alloc.$(OBJEXT) \
+	gsm_subscriber_base.$(OBJEXT) handover_decision.$(OBJEXT) \
+	handover_logic.$(OBJEXT) meas_rep.$(OBJEXT) \
+	rest_octets.$(OBJEXT) system_information.$(OBJEXT) \
+	e1_config.$(OBJEXT) transaction.$(OBJEXT) bsc_api.$(OBJEXT) \
+	bsc_msc.$(OBJEXT) bsc_vty.$(OBJEXT) gsm_04_08_utils.$(OBJEXT) \
+	bsc_init.$(OBJEXT)
+libbsc_a_OBJECTS = $(am_libbsc_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libbsc_a_SOURCES)
+DIST_SOURCES = $(libbsc_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libbsc.a
+libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \
+			abis_om2000.c abis_om2000_vty.c \
+			abis_rsl.c bsc_rll.c \
+			paging.c \
+			bts_ericsson_rbs2000.c \
+			bts_ipaccess_nanobts.c \
+			bts_siemens_bs11.c \
+			bts_hsl_femtocell.c \
+			bts_unknown.c \
+			chan_alloc.c \
+			gsm_subscriber_base.c \
+			handover_decision.c handover_logic.c meas_rep.c \
+			rest_octets.c system_information.c \
+			e1_config.c \
+			transaction.c \
+			bsc_api.c bsc_msc.c bsc_vty.c \
+			gsm_04_08_utils.c \
+			bsc_init.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libbsc/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libbsc/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libbsc.a: $(libbsc_a_OBJECTS) $(libbsc_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libbsc.a
+	$(AM_V_AR)$(libbsc_a_AR) libbsc.a $(libbsc_a_OBJECTS) $(libbsc_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libbsc.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_rsl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_api.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_init.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_msc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_rll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ericsson_rbs2000.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_hsl_femtocell.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ipaccess_nanobts.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_siemens_bs11.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_unknown.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chan_alloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_08_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_subscriber_base.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_decision.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_logic.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meas_rep.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paging.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rest_octets.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/system_information.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libbsc/abis_nm.c b/src/libbsc/abis_nm.c
new file mode 100644
index 0000000..0e7fc8d
--- /dev/null
+++ b/src/libbsc/abis_nm.c
@@ -0,0 +1,3132 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/misdn.h>
+#include <openbsc/signal.h>
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+#define IPACC_SEGMENT_SIZE	245
+
+/* unidirectional messages from BTS to BSC */
+static const enum abis_nm_msgtype reports[] = {
+	NM_MT_SW_ACTIVATED_REP,
+	NM_MT_TEST_REP,
+	NM_MT_STATECHG_EVENT_REP,
+	NM_MT_FAILURE_EVENT_REP,
+};
+
+/* messages without ACK/NACK */
+static const enum abis_nm_msgtype no_ack_nack[] = {
+	NM_MT_MEAS_RES_REQ,
+	NM_MT_STOP_MEAS,
+	NM_MT_START_MEAS,
+};
+
+/* Messages related to software load */
+static const enum abis_nm_msgtype sw_load_msgs[] = {
+	NM_MT_LOAD_INIT_ACK,
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_SEG_ACK,
+	NM_MT_LOAD_ABORT,
+	NM_MT_LOAD_END_ACK,
+	NM_MT_LOAD_END_NACK,
+	//NM_MT_SW_ACT_REQ,
+	NM_MT_ACTIVATE_SW_ACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_SW_ACTIVATED_REP,
+};
+
+static const enum abis_nm_msgtype nacks[] = {
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_END_NACK,
+	NM_MT_SW_ACT_REQ_NACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_ESTABLISH_TEI_NACK,
+	NM_MT_CONN_TERR_SIGN_NACK,
+	NM_MT_DISC_TERR_SIGN_NACK,
+	NM_MT_CONN_TERR_TRAF_NACK,
+	NM_MT_DISC_TERR_TRAF_NACK,
+	NM_MT_CONN_MDROP_LINK_NACK,
+	NM_MT_DISC_MDROP_LINK_NACK,
+	NM_MT_SET_BTS_ATTR_NACK,
+	NM_MT_SET_RADIO_ATTR_NACK,
+	NM_MT_SET_CHAN_ATTR_NACK,
+	NM_MT_PERF_TEST_NACK,
+	NM_MT_SEND_TEST_REP_NACK,
+	NM_MT_STOP_TEST_NACK,
+	NM_MT_STOP_EVENT_REP_NACK,
+	NM_MT_REST_EVENT_REP_NACK,
+	NM_MT_CHG_ADM_STATE_NACK,
+	NM_MT_CHG_ADM_STATE_REQ_NACK,
+	NM_MT_REP_OUTST_ALARMS_NACK,
+	NM_MT_CHANGEOVER_NACK,
+	NM_MT_OPSTART_NACK,
+	NM_MT_REINIT_NACK,
+	NM_MT_SET_SITE_OUT_NACK,
+	NM_MT_CHG_HW_CONF_NACK,
+	NM_MT_GET_ATTR_NACK,
+	NM_MT_SET_ALARM_THRES_NACK,
+	NM_MT_BS11_BEGIN_DB_TX_NACK,
+	NM_MT_BS11_END_DB_TX_NACK,
+	NM_MT_BS11_CREATE_OBJ_NACK,
+	NM_MT_BS11_DELETE_OBJ_NACK,
+};
+
+static const struct value_string nack_names[] = {
+	{ NM_MT_LOAD_INIT_NACK,		"SOFTWARE LOAD INIT" },
+	{ NM_MT_LOAD_END_NACK,		"SOFTWARE LOAD END" },
+	{ NM_MT_SW_ACT_REQ_NACK,	"SOFTWARE ACTIVATE REQUEST" },
+	{ NM_MT_ACTIVATE_SW_NACK,	"ACTIVATE SOFTWARE" },
+	{ NM_MT_ESTABLISH_TEI_NACK,	"ESTABLISH TEI" },
+	{ NM_MT_CONN_TERR_SIGN_NACK,	"CONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_DISC_TERR_SIGN_NACK,	"DISCONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_CONN_TERR_TRAF_NACK,	"CONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_DISC_TERR_TRAF_NACK,	"DISCONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_CONN_MDROP_LINK_NACK,	"CONNECT MULTI-DROP LINK" },
+	{ NM_MT_DISC_MDROP_LINK_NACK,	"DISCONNECT MULTI-DROP LINK" },
+	{ NM_MT_SET_BTS_ATTR_NACK,	"SET BTS ATTRIBUTE" },
+	{ NM_MT_SET_RADIO_ATTR_NACK,	"SET RADIO ATTRIBUTE" },
+	{ NM_MT_SET_CHAN_ATTR_NACK,	"SET CHANNEL ATTRIBUTE" },
+	{ NM_MT_PERF_TEST_NACK,		"PERFORM TEST" },
+	{ NM_MT_SEND_TEST_REP_NACK,	"SEND TEST REPORT" },
+	{ NM_MT_STOP_TEST_NACK,		"STOP TEST" },
+	{ NM_MT_STOP_EVENT_REP_NACK,	"STOP EVENT REPORT" },
+	{ NM_MT_REST_EVENT_REP_NACK,	"RESET EVENT REPORT" },
+	{ NM_MT_CHG_ADM_STATE_NACK,	"CHANGE ADMINISTRATIVE STATE" },
+	{ NM_MT_CHG_ADM_STATE_REQ_NACK,
+				"CHANGE ADMINISTRATIVE STATE REQUEST" },
+	{ NM_MT_REP_OUTST_ALARMS_NACK,	"REPORT OUTSTANDING ALARMS" },
+	{ NM_MT_CHANGEOVER_NACK,	"CHANGEOVER" },
+	{ NM_MT_OPSTART_NACK,		"OPSTART" },
+	{ NM_MT_REINIT_NACK,		"REINIT" },
+	{ NM_MT_SET_SITE_OUT_NACK,	"SET SITE OUTPUT" },
+	{ NM_MT_CHG_HW_CONF_NACK,	"CHANGE HARDWARE CONFIGURATION" },
+	{ NM_MT_GET_ATTR_NACK,		"GET ATTRIBUTE" },
+	{ NM_MT_SET_ALARM_THRES_NACK,	"SET ALARM THRESHOLD" },
+	{ NM_MT_BS11_BEGIN_DB_TX_NACK,	"BS11 BEGIN DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_END_DB_TX_NACK,	"BS11 END DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_CREATE_OBJ_NACK,	"BS11 CREATE OBJECT" },
+	{ NM_MT_BS11_DELETE_OBJ_NACK,	"BS11 DELETE OBJECT" },
+	{ 0,				NULL }
+};
+
+/* Chapter 9.4.36 */
+static const struct value_string nack_cause_names[] = {
+	/* General Nack Causes */
+	{ NM_NACK_INCORR_STRUCT,	"Incorrect message structure" },
+	{ NM_NACK_MSGTYPE_INVAL,	"Invalid message type value" },
+	{ NM_NACK_OBJCLASS_INVAL,	"Invalid Object class value" },
+	{ NM_NACK_OBJCLASS_NOTSUPP,	"Object class not supported" },
+	{ NM_NACK_BTSNR_UNKN,		"BTS no. unknown" },
+	{ NM_NACK_TRXNR_UNKN,		"Baseband Transceiver no. unknown" },
+	{ NM_NACK_OBJINST_UNKN,		"Object Instance unknown" },
+	{ NM_NACK_ATTRID_INVAL,		"Invalid attribute identifier value" },
+	{ NM_NACK_ATTRID_NOTSUPP,	"Attribute identifier not supported" },
+	{ NM_NACK_PARAM_RANGE,		"Parameter value outside permitted range" },
+	{ NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" },
+	{ NM_NACK_SPEC_IMPL_NOTSUPP,	"Specified implementation not supported" },
+	{ NM_NACK_CANT_PERFORM,		"Message cannot be performed" },
+	/* Specific Nack Causes */
+	{ NM_NACK_RES_NOTIMPL,		"Resource not implemented" },
+	{ NM_NACK_RES_NOTAVAIL,		"Resource not available" },
+	{ NM_NACK_FREQ_NOTAVAIL,	"Frequency not available" },
+	{ NM_NACK_TEST_NOTSUPP,		"Test not supported" },
+	{ NM_NACK_CAPACITY_RESTR,	"Capacity restrictions" },
+	{ NM_NACK_PHYSCFG_NOTPERFORM,	"Physical configuration cannot be performed" },
+	{ NM_NACK_TEST_NOTINIT,		"Test not initiated" },
+	{ NM_NACK_PHYSCFG_NOTRESTORE,	"Physical configuration cannot be restored" },
+	{ NM_NACK_TEST_NOSUCH,		"No such test" },
+	{ NM_NACK_TEST_NOSTOP,		"Test cannot be stopped" },
+	{ NM_NACK_MSGINCONSIST_PHYSCFG,	"Message inconsistent with physical configuration" },
+	{ NM_NACK_FILE_INCOMPLETE,	"Complete file notreceived" },
+	{ NM_NACK_FILE_NOTAVAIL,	"File not available at destination" },
+	{ NM_NACK_FILE_NOTACTIVATE,	"File cannot be activate" },
+	{ NM_NACK_REQ_NOT_GRANT,	"Request not granted" },
+	{ NM_NACK_WAIT,			"Wait" },
+	{ NM_NACK_NOTH_REPORT_EXIST,	"Nothing reportable existing" },
+	{ NM_NACK_MEAS_NOTSUPP,		"Measurement not supported" },
+	{ NM_NACK_MEAS_NOTSTART,	"Measurement not started" },
+	{ 0,				NULL }
+};
+
+static const char *nack_cause_name(u_int8_t cause)
+{
+	return get_value_string(nack_cause_names, cause);
+}
+
+/* Chapter 9.4.16: Event Type */
+static const struct value_string event_type_names[] = {
+	{ NM_EVT_COMM_FAIL,		"communication failure" },
+	{ NM_EVT_QOS_FAIL,		"quality of service failure" },
+	{ NM_EVT_PROC_FAIL,		"processing failure" },
+	{ NM_EVT_EQUIP_FAIL,		"equipment failure" },
+	{ NM_EVT_ENV_FAIL,		"environment failure" },
+	{ 0,				NULL }
+};
+
+static const char *event_type_name(u_int8_t cause)
+{
+	return get_value_string(event_type_names, cause);
+}
+
+/* Chapter 9.4.63: Perceived Severity */
+static const struct value_string severity_names[] = {
+	{ NM_SEVER_CEASED,		"failure ceased" },
+	{ NM_SEVER_CRITICAL,		"critical failure" },
+	{ NM_SEVER_MAJOR,		"major failure" },
+	{ NM_SEVER_MINOR,		"minor failure" },
+	{ NM_SEVER_WARNING,		"warning level failure" },
+	{ NM_SEVER_INDETERMINATE,	"indeterminate failure" },
+	{ 0,				NULL }
+};
+
+static const char *severity_name(u_int8_t cause)
+{
+	return get_value_string(severity_names, cause);
+}
+
+/* Attributes that the BSC can set, not only get, according to Section 9.4 */
+static const enum abis_nm_attr nm_att_settable[] = {
+	NM_ATT_ADD_INFO,
+	NM_ATT_ADD_TEXT,
+	NM_ATT_DEST,
+	NM_ATT_EVENT_TYPE,
+	NM_ATT_FILE_DATA,
+	NM_ATT_GET_ARI,
+	NM_ATT_HW_CONF_CHG,
+	NM_ATT_LIST_REQ_ATTR,
+	NM_ATT_MDROP_LINK,
+	NM_ATT_MDROP_NEXT,
+	NM_ATT_NACK_CAUSES,
+	NM_ATT_OUTST_ALARM,
+	NM_ATT_PHYS_CONF,
+	NM_ATT_PROB_CAUSE,
+	NM_ATT_RAD_SUBC,
+	NM_ATT_SOURCE,
+	NM_ATT_SPEC_PROB,
+	NM_ATT_START_TIME,
+	NM_ATT_TEST_DUR,
+	NM_ATT_TEST_NO,
+	NM_ATT_TEST_REPORT,
+	NM_ATT_WINDOW_SIZE,
+	NM_ATT_SEVERITY,
+	NM_ATT_MEAS_RES,
+	NM_ATT_MEAS_TYPE,
+};
+
+const struct tlv_definition nm_att_tlvdef = {
+	.def = {
+		[NM_ATT_ABIS_CHANNEL] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_ADD_INFO] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADD_TEXT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADM_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_ARFCN_LIST]=		{ TLV_TYPE_TL16V },
+		[NM_ATT_AUTON_REPORT] =		{ TLV_TYPE_TV },
+		[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_BCCH_ARFCN] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_BSIC] =			{ TLV_TYPE_TV },
+		[NM_ATT_BTS_AIR_TIMER] =	{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_I_P] =		{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_T] =		{ TLV_TYPE_TV },
+		[NM_ATT_CHAN_COMB] =		{ TLV_TYPE_TV },
+		[NM_ATT_CONN_FAIL_CRIT] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_DEST] =			{ TLV_TYPE_TL16V },
+		[NM_ATT_EVENT_TYPE] =		{ TLV_TYPE_TV },
+		[NM_ATT_FILE_DATA] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_GSM_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_HSN] =			{ TLV_TYPE_TV },
+		[NM_ATT_HW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_DESC] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_INTAVE_PARAM] =		{ TLV_TYPE_TV },
+		[NM_ATT_INTERF_BOUND] =		{ TLV_TYPE_FIXED, 6 },
+		[NM_ATT_LIST_REQ_ATTR] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_MAIO] =			{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_THRESH] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MANUF_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MAX_TA] =		{ TLV_TYPE_TV },
+		[NM_ATT_MDROP_LINK] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_MDROP_NEXT] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_NACK_CAUSES] =		{ TLV_TYPE_TV },
+		[NM_ATT_NY1] =			{ TLV_TYPE_TV },
+		[NM_ATT_OPER_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_OVERL_PERIOD] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_PHYS_CONF] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_POWER_CLASS] =		{ TLV_TYPE_TV },
+		[NM_ATT_POWER_THRESH] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_PROB_CAUSE] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_RACH_B_THRESH] =	{ TLV_TYPE_TV },
+		[NM_ATT_LDAVG_SLOTS] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_RAD_SUBC] =		{ TLV_TYPE_TV },
+		[NM_ATT_RF_MAXPOWR_R] =		{ TLV_TYPE_TV },
+		[NM_ATT_SITE_INPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SITE_OUTPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SOURCE] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SPEC_PROB] =		{ TLV_TYPE_TV },
+		[NM_ATT_START_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_T200] =			{ TLV_TYPE_FIXED, 7 },
+		[NM_ATT_TEI] =			{ TLV_TYPE_TV },
+		[NM_ATT_TEST_DUR] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_TEST_NO] =		{ TLV_TYPE_TV },
+		[NM_ATT_TEST_REPORT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_VSWR_THRESH] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_WINDOW_SIZE] = 		{ TLV_TYPE_TV },
+		[NM_ATT_TSC] =			{ TLV_TYPE_TV },
+		[NM_ATT_SW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SEVERITY] = 		{ TLV_TYPE_TV },
+		[NM_ATT_GET_ARI] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_CONF_CHG] = 		{ TLV_TYPE_TL16V },
+		[NM_ATT_OUTST_ALARM] =		{ TLV_TYPE_TV },
+		[NM_ATT_MEAS_RES] =		{ TLV_TYPE_TL16V },
+	},
+};
+
+static const enum abis_nm_chan_comb chcomb4pchan[] = {
+	[GSM_PCHAN_CCCH]	= NM_CHANC_mainBCCH,
+	[GSM_PCHAN_CCCH_SDCCH4]	= NM_CHANC_BCCHComb,
+	[GSM_PCHAN_TCH_F]	= NM_CHANC_TCHFull,
+	[GSM_PCHAN_TCH_H]	= NM_CHANC_TCHHalf,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = NM_CHANC_SDCCH,
+	[GSM_PCHAN_PDCH]	= NM_CHANC_IPAC_PDCH,
+	[GSM_PCHAN_TCH_F_PDCH]	= NM_CHANC_IPAC_TCHFull_PDCH,
+	/* FIXME: bounds check */
+};
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan)
+{
+	if (pchan < ARRAY_SIZE(chcomb4pchan))
+		return chcomb4pchan[pchan];
+
+	return -EINVAL;
+}
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_t *buf, int len)
+{
+	if (!bts->model)
+		return -EIO;
+	return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0);
+}
+
+static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size)
+{
+	int i;
+
+	for (i = 0; i < size; i++) {
+		if (arr[i] == mt)
+			return 1;
+	}
+
+	return 0;
+}
+
+#if 0
+/* is this msgtype the usual ACK/NACK type ? */
+static int is_ack_nack(enum abis_nm_msgtype mt)
+{
+	return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack));
+}
+#endif
+
+/* is this msgtype a report ? */
+static int is_report(enum abis_nm_msgtype mt)
+{
+	return is_in_arr(mt, reports, ARRAY_SIZE(reports));
+}
+
+#define MT_ACK(x)	(x+1)
+#define MT_NACK(x)	(x+2)
+
+static void fill_om_hdr(struct abis_om_hdr *oh, u_int8_t len)
+{
+	oh->mdisc = ABIS_OM_MDISC_FOM;
+	oh->placement = ABIS_OM_PLACEMENT_ONLY;
+	oh->sequence = 0;
+	oh->length = len;
+}
+
+static void fill_om_fom_hdr(struct abis_om_hdr *oh, u_int8_t len,
+			    u_int8_t msg_type, u_int8_t obj_class,
+			    u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr)
+{
+	struct abis_om_fom_hdr *foh =
+			(struct abis_om_fom_hdr *) oh->data;
+
+	fill_om_hdr(oh, len+sizeof(*foh));
+	foh->msg_type = msg_type;
+	foh->obj_class = obj_class;
+	foh->obj_inst.bts_nr = bts_nr;
+	foh->obj_inst.trx_nr = trx_nr;
+	foh->obj_inst.ts_nr = ts_nr;
+}
+
+static struct msgb *nm_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OML");
+}
+
+/* Send a OML NM Message from BSC to BTS */
+static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg)
+{
+	msg->trx = bts->c0;
+
+	/* queue OML messages */
+	if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) {
+		bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg);
+		return _abis_nm_sendmsg(msg, 0);
+	} else {
+		msgb_enqueue(&bts->abis_queue, msg);
+		return 0;
+	}
+
+}
+
+int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	OBSC_NM_W_ACK_CB(msg) = 1;
+	return abis_nm_queue_msg(bts, msg);
+}
+
+static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg)
+{
+	OBSC_NM_W_ACK_CB(msg) = 0;
+	return abis_nm_queue_msg(bts, msg);
+}
+
+static int abis_nm_rcvmsg_sw(struct msgb *mb);
+
+const struct value_string abis_nm_obj_class_names[] = {
+	{ NM_OC_SITE_MANAGER,	"SITE-MANAGER" },
+	{ NM_OC_BTS,		"BTS" },
+	{ NM_OC_RADIO_CARRIER,	"RADIO-CARRIER" },
+	{ NM_OC_BASEB_TRANSC,	"BASEBAND-TRANSCEIVER" },
+	{ NM_OC_CHANNEL,	"CHANNEL" },
+	{ NM_OC_BS11_ADJC,	"ADJC" },
+	{ NM_OC_BS11_HANDOVER,	"HANDOVER" },
+	{ NM_OC_BS11_PWR_CTRL,	"POWER-CONTROL" },
+	{ NM_OC_BS11_BTSE,	"BTSE" },
+	{ NM_OC_BS11_RACK,	"RACK" },
+	{ NM_OC_BS11_TEST,	"TEST" },
+	{ NM_OC_BS11_ENVABTSE,	"ENVABTSE" },
+	{ NM_OC_BS11_BPORT,	"BPORT" },
+	{ NM_OC_GPRS_NSE,	"GPRS-NSE" },
+	{ NM_OC_GPRS_CELL,	"GPRS-CELL" },
+	{ NM_OC_GPRS_NSVC,	"GPRS-NSVC" },
+	{ NM_OC_BS11,		"SIEMENSHW" },
+	{ 0,			NULL }
+};
+
+static const char *obj_class_name(u_int8_t oc)
+{
+	return get_value_string(abis_nm_obj_class_names, oc);
+}
+
+const char *nm_opstate_name(u_int8_t os)
+{
+	switch (os) {
+	case NM_OPSTATE_DISABLED:
+		return "Disabled";
+	case NM_OPSTATE_ENABLED:
+		return "Enabled";
+	case NM_OPSTATE_NULL:
+		return "NULL";
+	default:
+		return "RFU";
+	}
+}
+
+/* Chapter 9.4.7 */
+static const struct value_string avail_names[] = {
+	{ 0, 	"In test" },
+	{ 1,	"Failed" },
+	{ 2,	"Power off" },
+	{ 3,	"Off line" },
+	/* Not used */
+	{ 5,	"Dependency" },
+	{ 6,	"Degraded" },
+	{ 7,	"Not installed" },
+	{ 0xff, "OK" },
+	{ 0,	NULL }
+};
+
+const char *nm_avail_name(u_int8_t avail)
+{
+	return get_value_string(avail_names, avail);
+}
+
+static struct value_string test_names[] = {
+	/* FIXME: standard test names */
+	{ NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" },
+	{ NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
+	{ NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" },
+	{ NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" },
+	{ NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" },
+	{ NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" },
+	{ NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" },
+	{ 0, NULL }
+};
+
+const struct value_string abis_nm_adm_state_names[] = {
+	{ NM_STATE_LOCKED,	"Locked" },
+	{ NM_STATE_UNLOCKED,	"Unlocked" },
+	{ NM_STATE_SHUTDOWN,	"Shutdown" },
+	{ NM_STATE_NULL,	"NULL" },
+	{ 0, NULL }
+};
+
+const char *nm_adm_name(u_int8_t adm)
+{
+	return get_value_string(abis_nm_adm_state_names, adm);
+}
+
+int nm_is_running(struct gsm_nm_state *s) {
+	return (s->operational == NM_OPSTATE_ENABLED) && (
+		(s->availability == NM_AVSTATE_OK) ||
+		(s->availability == 0xff)
+	);
+}
+
+static void debugp_foh(struct abis_om_fom_hdr *foh)
+{
+	DEBUGP(DNM, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
+		obj_class_name(foh->obj_class), foh->obj_class,
+		foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
+		foh->obj_inst.ts_nr);
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+static struct gsm_nm_state *
+objclass2nmstate(struct gsm_bts *bts, u_int8_t obj_class,
+		 struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_nm_state *nm_state = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		nm_state = &bts->nm_state;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		nm_state = &trx->nm_state;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		nm_state = &trx->bb_transc.nm_state;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		nm_state = &trx->ts[obj_inst->ts_nr].nm_state;
+		break;
+	case NM_OC_SITE_MANAGER:
+		nm_state = &bts->site_mgr.nm_state;
+		break;
+	case NM_OC_BS11:
+		switch (obj_inst->bts_nr) {
+		case BS11_OBJ_CCLK:
+			nm_state = &bts->bs11.cclk.nm_state;
+			break;
+		case BS11_OBJ_BBSIG:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			nm_state = &trx->bs11.bbsig.nm_state;
+			break;
+		case BS11_OBJ_PA:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			nm_state = &trx->bs11.pa.nm_state;
+			break;
+		default:
+			return NULL;
+		}
+	case NM_OC_BS11_RACK:
+		nm_state = &bts->bs11.rack.nm_state;
+		break;
+	case NM_OC_BS11_ENVABTSE:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+			return NULL;
+		nm_state = &bts->bs11.envabtse[obj_inst->trx_nr].nm_state;
+		break;
+	case NM_OC_GPRS_NSE:
+		nm_state = &bts->gprs.nse.nm_state;
+		break;
+	case NM_OC_GPRS_CELL:
+		nm_state = &bts->gprs.cell.nm_state;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		nm_state = &bts->gprs.nsvc[obj_inst->trx_nr].nm_state;
+		break;
+	}
+	return nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+static void *
+objclass2obj(struct gsm_bts *bts, u_int8_t obj_class,
+	     struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	void *obj = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		obj = bts;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = trx;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = &trx->bb_transc;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		obj = &trx->ts[obj_inst->ts_nr];
+		break;
+	case NM_OC_SITE_MANAGER:
+		obj = &bts->site_mgr;
+		break;
+	case NM_OC_GPRS_NSE:
+		obj = &bts->gprs.nse;
+		break;
+	case NM_OC_GPRS_CELL:
+		obj = &bts->gprs.cell;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+		break;
+	}
+	return obj;
+}
+
+/* Update the administrative state of a given object in our in-memory data
+ * structures and send an event to the higher layer */
+static int update_admstate(struct gsm_bts *bts, u_int8_t obj_class,
+			   struct abis_om_obj_inst *obj_inst, u_int8_t adm_state)
+{
+	struct gsm_nm_state *nm_state, new_state;
+	struct nm_statechg_signal_data nsd;
+
+	nsd.obj = objclass2obj(bts, obj_class, obj_inst);
+	if (!nsd.obj)
+		return -EINVAL;
+	nm_state = objclass2nmstate(bts, obj_class, obj_inst);
+	if (!nm_state)
+		return -1;
+
+	new_state = *nm_state;
+	new_state.administrative = adm_state;
+
+	nsd.obj_class = obj_class;
+	nsd.old_state = nm_state;
+	nsd.new_state = &new_state;
+	nsd.obj_inst = obj_inst;
+	dispatch_signal(SS_NM, S_NM_STATECHG_ADM, &nsd);
+
+	nm_state->administrative = adm_state;
+
+	return 0;
+}
+
+static int abis_nm_rx_statechg_rep(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct gsm_bts *bts = mb->trx->bts;
+	struct tlv_parsed tp;
+	struct gsm_nm_state *nm_state, new_state;
+
+	DEBUGPC(DNM, "STATE CHG: ");
+
+	memset(&new_state, 0, sizeof(new_state));
+
+	nm_state = objclass2nmstate(bts, foh->obj_class, &foh->obj_inst);
+	if (!nm_state) {
+		DEBUGPC(DNM, "unknown object class\n");
+		return -EINVAL;
+	}
+
+	new_state = *nm_state;
+	
+	abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+	if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) {
+		new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE);
+		DEBUGPC(DNM, "OP_STATE=%s ", nm_opstate_name(new_state.operational));
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) {
+		if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0)
+			new_state.availability = 0xff;
+		else
+			new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS);
+		DEBUGPC(DNM, "AVAIL=%s(%02x) ", nm_avail_name(new_state.availability),
+			new_state.availability);
+	} else
+		new_state.availability = 0xff;
+	if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
+		new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+		DEBUGPC(DNM, "ADM=%2s ", nm_adm_name(new_state.administrative));
+	}
+	DEBUGPC(DNM, "\n");
+
+	if ((new_state.administrative != 0 && nm_state->administrative == 0) ||
+	    new_state.operational != nm_state->operational ||
+	    new_state.availability != nm_state->availability) {
+		/* Update the operational state of a given object in our in-memory data
+ 		* structures and send an event to the higher layer */
+		struct nm_statechg_signal_data nsd;
+		nsd.obj = objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+		nsd.obj_class = foh->obj_class;
+		nsd.old_state = nm_state;
+		nsd.new_state = &new_state;
+		nsd.obj_inst = &foh->obj_inst;
+		dispatch_signal(SS_NM, S_NM_STATECHG_OPER, &nsd);
+		nm_state->operational = new_state.operational;
+		nm_state->availability = new_state.availability;
+		if (nm_state->administrative == 0)
+			nm_state->administrative = new_state.administrative;
+	}
+#if 0
+	if (op_state == 1) {
+		/* try to enable objects that are disabled */
+		abis_nm_opstart(bts, foh->obj_class,
+				foh->obj_inst.bts_nr,
+				foh->obj_inst.trx_nr,
+				foh->obj_inst.ts_nr);
+	}
+#endif
+	return 0;
+}
+
+static int rx_fail_evt_rep(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+	const uint8_t *p_val;
+	char *p_text;
+
+	DEBUGPC(DNM, "Failure Event Report ");
+	
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
+		DEBUGPC(DNM, "Type=%s ", event_type_name(*TLVP_VAL(&tp, NM_ATT_EVENT_TYPE)));
+	if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
+		DEBUGPC(DNM, "Severity=%s ", severity_name(*TLVP_VAL(&tp, NM_ATT_SEVERITY)));
+	if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) {
+		p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE);
+		DEBUGPC(DNM, "Probable cause= %02X %02X %02X ", p_val[0], p_val[1], p_val[2]);
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) {
+		p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT);
+		p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val, TLVP_LEN(&tp, NM_ATT_ADD_TEXT));
+		if (p_text) {
+			DEBUGPC(DNM, "Additional Text=%s ", p_text);
+			talloc_free(p_text);
+		}
+	}
+
+	DEBUGPC(DNM, "\n");
+
+	return 0;
+}
+
+static int abis_nm_rcvmsg_report(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	u_int8_t mt = foh->msg_type;
+
+	debugp_foh(foh);
+
+	//nmh->cfg->report_cb(mb, foh);
+
+	switch (mt) {
+	case NM_MT_STATECHG_EVENT_REP:
+		return abis_nm_rx_statechg_rep(mb);
+		break;
+	case NM_MT_SW_ACTIVATED_REP:
+		DEBUGPC(DNM, "Software Activated Report\n");
+		dispatch_signal(SS_NM, S_NM_SW_ACTIV_REP, mb);
+		break;
+	case NM_MT_FAILURE_EVENT_REP:
+		rx_fail_evt_rep(mb);
+		dispatch_signal(SS_NM, S_NM_FAIL_REP, mb);
+		break;
+	case NM_MT_TEST_REP:
+		DEBUGPC(DNM, "Test Report\n");
+		dispatch_signal(SS_NM, S_NM_TEST_REP, mb);
+		break;
+	default:
+		DEBUGPC(DNM, "reporting NM MT 0x%02x\n", mt);
+		break;
+		
+	};
+
+	return 0;
+}
+
+/* Activate the specified software into the BTS */
+static int ipacc_sw_activate(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1,
+			     u_int8_t i2, const u_int8_t *sw_desc, u_int8_t swdesc_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = swdesc_len;
+	u_int8_t *trailer;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2);
+
+	trailer = msgb_put(msg, swdesc_len);
+	memcpy(trailer, sw_desc, swdesc_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+static int abis_nm_parse_sw_descr(const u_int8_t *sw_descr, int sw_descr_len)
+{
+	static const struct tlv_definition sw_descr_def = {
+		.def = {
+			[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V, },
+			[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V, },
+		},
+	};
+
+	u_int8_t tag;
+	u_int16_t tag_len;
+	const u_int8_t *val;
+	int ofs = 0, len;
+
+	/* Classic TLV parsing doesn't work well with SW_DESCR because of it's
+	 * nested nature and the fact you have to assume it contains only two sub
+	 * tags NM_ATT_FILE_VERSION & NM_ATT_FILE_ID to parse it */
+
+	if (sw_descr[0] != NM_ATT_SW_DESCR) {
+		DEBUGP(DNM, "SW_DESCR attribute identifier not found!\n");
+		return -1;
+	}
+	ofs += 1;
+
+	len = tlv_parse_one(&tag, &tag_len, &val,
+		&sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+	if (len < 0 || (tag != NM_ATT_FILE_ID)) {
+		DEBUGP(DNM, "FILE_ID attribute identifier not found!\n");
+		return -2;
+	}
+	ofs += len;
+
+	len = tlv_parse_one(&tag, &tag_len, &val,
+		&sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+	if (len < 0 || (tag != NM_ATT_FILE_VERSION)) {
+		DEBUGP(DNM, "FILE_VERSION attribute identifier not found!\n");
+		return -3;
+	}
+	ofs += len;
+
+	return ofs;
+}
+
+static int abis_nm_rx_sw_act_req(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+	const u_int8_t *sw_config;
+	int ret, sw_config_len, sw_descr_len;
+
+	debugp_foh(foh);
+
+	DEBUGPC(DNM, "SW Activate Request: ");
+
+	DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n");
+
+	ret = abis_nm_sw_act_req_ack(mb->trx->bts, foh->obj_class,
+				      foh->obj_inst.bts_nr,
+				      foh->obj_inst.trx_nr,
+				      foh->obj_inst.ts_nr, 0,
+				      foh->data, oh->length-sizeof(*foh));
+
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+	sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG);
+	sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG);
+	if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) {
+		DEBUGP(DNM, "SW config not found! Can't continue.\n");
+		return -EINVAL;
+	} else {
+		DEBUGP(DNM, "Found SW config: %s\n", hexdump(sw_config, sw_config_len));
+	}
+
+		/* Use the first SW_DESCR present in SW config */
+	sw_descr_len = abis_nm_parse_sw_descr(sw_config, sw_config_len);
+	if (sw_descr_len < 0)
+		return -EINVAL;
+
+	return ipacc_sw_activate(mb->trx->bts, foh->obj_class,
+				 foh->obj_inst.bts_nr,
+				 foh->obj_inst.trx_nr,
+				 foh->obj_inst.ts_nr,
+				 sw_config, sw_descr_len);
+}
+
+/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */
+static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+	u_int8_t adm_state;
+
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+	if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE))
+		return -EINVAL;
+
+	adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+
+	return update_admstate(mb->trx->bts, foh->obj_class, &foh->obj_inst, adm_state);
+}
+
+static int abis_nm_rx_lmt_event(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+
+	DEBUGP(DNM, "LMT Event ");
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) {
+		u_int8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION);
+		DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF");
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) {
+		u_int8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV);
+		DEBUGPC(DNM, "Level=%u ", level);
+	}
+	if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) &&
+	    TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) {
+		char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME);
+		DEBUGPC(DNM, "Username=%s ", name);
+	}
+	DEBUGPC(DNM, "\n");
+	/* FIXME: parse LMT LOGON TIME */
+	return 0;
+}
+
+static void abis_nm_queue_send_next(struct gsm_bts *bts)
+{
+	int wait = 0;
+	struct msgb *msg;
+	/* the queue is empty */
+	while (!llist_empty(&bts->abis_queue)) {
+		msg = msgb_dequeue(&bts->abis_queue);
+		wait = OBSC_NM_W_ACK_CB(msg);
+		_abis_nm_sendmsg(msg, 0);
+
+		if (wait)
+			break;
+	}
+
+	bts->abis_nm_pend = wait;
+}
+
+/* Receive a OML NM Message from BTS */
+static int abis_nm_rcvmsg_fom(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	u_int8_t mt = foh->msg_type;
+	int ret = 0;
+
+	/* check for unsolicited message */
+	if (is_report(mt))
+		return abis_nm_rcvmsg_report(mb);
+
+	if (is_in_arr(mt, sw_load_msgs, ARRAY_SIZE(sw_load_msgs)))
+		return abis_nm_rcvmsg_sw(mb);
+
+	if (is_in_arr(mt, nacks, ARRAY_SIZE(nacks))) {
+		struct nm_nack_signal_data nack_data;
+		struct tlv_parsed tp;
+
+		debugp_foh(foh);
+
+		DEBUGPC(DNM, "%s NACK ", get_value_string(nack_names, mt));
+
+		abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			DEBUGPC(DNM, "CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\n");
+
+		nack_data.msg = mb;
+		nack_data.mt = mt;
+		dispatch_signal(SS_NM, S_NM_NACK, &nack_data);
+		abis_nm_queue_send_next(mb->trx->bts);
+		return 0;
+	}
+#if 0
+	/* check if last message is to be acked */
+	if (is_ack_nack(nmh->last_msgtype)) {
+		if (mt == MT_ACK(nmh->last_msgtype)) {
+			DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type);
+			/* we got our ACK, continue sending the next msg */
+		} else if (mt == MT_NACK(nmh->last_msgtype)) {
+			/* we got a NACK, signal this to the caller */
+			DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type);
+			/* FIXME: somehow signal this to the caller */
+		} else {
+			/* really strange things happen */
+			return -EINVAL;
+		}
+	}
+#endif
+
+	switch (mt) {
+	case NM_MT_CHG_ADM_STATE_ACK:
+		ret = abis_nm_rx_chg_adm_state_ack(mb);
+		break;
+	case NM_MT_SW_ACT_REQ:
+		ret = abis_nm_rx_sw_act_req(mb);
+		break;
+	case NM_MT_BS11_LMT_SESSION:
+		ret = abis_nm_rx_lmt_event(mb);
+		break;
+	case NM_MT_CONN_MDROP_LINK_ACK:
+		DEBUGP(DNM, "CONN MDROP LINK ACK\n");
+		break;
+	case NM_MT_IPACC_RESTART_ACK:
+		dispatch_signal(SS_NM, S_NM_IPACC_RESTART_ACK, NULL);
+		break;
+	case NM_MT_IPACC_RESTART_NACK:
+		dispatch_signal(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
+		break;
+	case NM_MT_SET_BTS_ATTR_ACK:
+		/* The HSL wants an OPSTART _after_ the SI has been set */
+		if (mb->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) {
+			abis_nm_opstart(mb->trx->bts, NM_OC_BTS, 255, 255, 255);
+		}
+		break;
+	}
+
+	abis_nm_queue_send_next(mb->trx->bts);
+	return ret;
+}
+
+static int abis_nm_rx_ipacc(struct msgb *mb);
+
+static int abis_nm_rcvmsg_manuf(struct msgb *mb)
+{
+	int rc;
+	int bts_type = mb->trx->bts->type;
+
+	switch (bts_type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		rc = abis_nm_rx_ipacc(mb);
+		abis_nm_queue_send_next(mb->trx->bts);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this "
+		     "BTS type (%u)\n", bts_type);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+/* High-Level API */
+/* Entry-point where L2 OML from BTS enters the NM code */
+int abis_nm_rcvmsg(struct msgb *msg)
+{
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	int rc = 0;
+
+	/* Various consistency checks */
+	if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+			oh->placement);
+		if (oh->placement != ABIS_OM_PLACEMENT_FIRST)
+			return -EINVAL;
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+			oh->sequence);
+		return -EINVAL;
+	}
+#if 0
+	unsigned int l2_len = msg->tail - (u_int8_t *)msgb_l2(msg);
+	unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr);
+	if (oh->length + hlen > l2_len) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n",
+			oh->length + sizeof(*oh), l2_len);
+		return -EINVAL;
+	}
+	if (oh->length + hlen < l2_len)
+		LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len);
+#endif
+	msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+	switch (oh->mdisc) {
+	case ABIS_OM_MDISC_FOM:
+		rc = abis_nm_rcvmsg_fom(msg);
+		break;
+	case ABIS_OM_MDISC_MANUF:
+		rc = abis_nm_rcvmsg_manuf(msg);
+		break;
+	case ABIS_OM_MDISC_MMI:
+	case ABIS_OM_MDISC_TRAU:
+		LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n",
+			oh->mdisc);
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n",
+			oh->mdisc);
+		return -EINVAL;
+	}
+
+	msgb_free(msg);
+	return rc;
+}
+
+#if 0
+/* initialized all resources */
+struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg)
+{
+	struct abis_nm_h *nmh;
+
+	nmh = malloc(sizeof(*nmh));
+	if (!nmh)
+		return NULL;
+
+	nmh->cfg = cfg;
+
+	return nmh;
+}
+
+/* free all resources */
+void abis_nm_fini(struct abis_nm_h *nmh)
+{
+	free(nmh);
+}
+#endif
+
+/* Here we are trying to define a high-level API that can be used by
+ * the actual BSC implementation.  However, the architecture is currently
+ * still under design.  Ideally the calls to this API would be synchronous,
+ * while the underlying stack behind the APi runs in a traditional select
+ * based state machine.
+ */
+
+/* 6.2 Software Load: */
+enum sw_state {
+	SW_STATE_NONE,
+	SW_STATE_WAIT_INITACK,
+	SW_STATE_WAIT_SEGACK,
+	SW_STATE_WAIT_ENDACK,
+	SW_STATE_WAIT_ACTACK,
+	SW_STATE_ERROR,
+};
+
+struct abis_nm_sw {
+	struct gsm_bts *bts;
+	int trx_nr;
+	gsm_cbfn *cbfn;
+	void *cb_data;
+	int forced;
+
+	/* this will become part of the SW LOAD INITIATE */
+	u_int8_t obj_class;
+	u_int8_t obj_instance[3];
+
+	u_int8_t file_id[255];
+	u_int8_t file_id_len;
+
+	u_int8_t file_version[255];
+	u_int8_t file_version_len;
+
+	u_int8_t window_size;
+	u_int8_t seg_in_window;
+
+	int fd;
+	FILE *stream;
+	enum sw_state state;
+	int last_seg;
+};
+
+static struct abis_nm_sw g_sw;
+
+static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg)
+{
+	if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) {
+		msgb_v_put(msg, NM_ATT_SW_DESCR);
+		msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+		msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+			       sw->file_version);
+	} else if (sw->bts->type == GSM_BTS_TYPE_BS11) {
+		msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+		msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+			     sw->file_version);
+	} else {
+		LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n");
+	}
+}
+
+/* 6.2.1 / 8.3.1: Load Data Initiate */
+static int sw_load_init(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 3*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	sw_add_file_id_and_ver(sw, msg);
+	msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size);
+	
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+static int is_last_line(FILE *stream)
+{
+	char next_seg_buf[256];
+	long pos;
+
+	/* check if we're sending the last line */
+	pos = ftell(stream);
+	if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) {
+		fseek(stream, pos, SEEK_SET);
+		return 1;
+	}
+
+	fseek(stream, pos, SEEK_SET);
+	return 0;
+}
+
+/* 6.2.2 / 8.3.2 Load Data Segment */
+static int sw_load_segment(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	char seg_buf[256];
+	char *line_buf = seg_buf+2;
+	unsigned char *tlv;
+	u_int8_t len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) {
+			perror("fgets reading segment");
+			return -EINVAL;
+		}
+		seg_buf[0] = 0x00;
+
+		/* check if we're sending the last line */
+		sw->last_seg = is_last_line(sw->stream);
+		if (sw->last_seg)
+			seg_buf[1] = 0;
+		else
+			seg_buf[1] = 1 + sw->seg_in_window++;
+
+		len = strlen(line_buf) + 2;
+		tlv = msgb_put(msg, TLV_GROSS_LEN(len));
+		tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (u_int8_t *)seg_buf);
+		/* BS11 wants CR + LF in excess of the TLV length !?! */
+		tlv[1] -= 2;
+
+		/* we only now know the exact length for the OM hdr */
+		len = strlen(line_buf)+2;
+		break;
+	case GSM_BTS_TYPE_NANOBTS: {
+		static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough);
+		len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE);
+		if (len < 0) {
+			perror("read failed");
+			return -EINVAL;
+		}
+
+		if (len != IPACC_SEGMENT_SIZE)
+			sw->last_seg = 1;
+
+		++sw->seg_in_window;
+		msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const u_int8_t *) seg_buf);
+		len += 3;
+		break;
+	}
+	default:
+		LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n");
+		/* FIXME: Other BTS types */
+		return -1;
+	}
+
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	return abis_nm_sendmsg_direct(sw->bts, msg);
+}
+
+/* 6.2.4 / 8.3.4 Load Data End */
+static int sw_load_end(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	sw_add_file_id_and_ver(sw, msg);
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+/* Activate the specified software into the BTS */
+static int sw_activate(struct abis_nm_sw *sw)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class,
+			sw->obj_instance[0], sw->obj_instance[1],
+			sw->obj_instance[2]);
+
+	/* FIXME: this is BS11 specific format */
+	msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+	msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+		     sw->file_version);
+
+	return abis_nm_sendmsg(sw->bts, msg);
+}
+
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[4];
+	unsigned int header_length;
+	unsigned int file_length;
+} __attribute__ ((packed));
+
+static int parse_sdp_header(struct abis_nm_sw *sw)
+{
+	struct sdp_firmware firmware_header;
+	int rc;
+	struct stat stat;
+
+	rc = read(sw->fd, &firmware_header, sizeof(firmware_header));
+	if (rc != sizeof(firmware_header)) {
+		LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n");
+		return -1;
+	}
+
+	if (strncmp(firmware_header.magic, " SDP", 4) != 0) {
+		LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n");
+		return -1;
+	}
+
+	if (firmware_header.more_magic[0] != 0x10 ||
+	    firmware_header.more_magic[1] != 0x02 ||
+	    firmware_header.more_magic[2] != 0x00 ||
+	    firmware_header.more_magic[3] != 0x00) {
+		LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n");
+		return -1;
+	}
+
+
+	if (fstat(sw->fd, &stat) == -1) {
+		LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n");
+		return -1;
+	}
+
+	if (ntohl(firmware_header.file_length) != stat.st_size) {
+		LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n");
+		return -1;
+	}
+
+	/* go back to the start as we checked the whole filesize.. */
+	lseek(sw->fd, 0l, SEEK_SET);
+	LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood.\n"
+			       "There might be checksums in the file that are not\n"
+			       "verified and incomplete firmware might be flashed.\n"
+			       "There is absolutely no WARRANTY that flashing will\n"
+			       "work.\n");
+	return 0;
+}
+
+static int sw_open_file(struct abis_nm_sw *sw, const char *fname)
+{
+	char file_id[12+1];
+	char file_version[80+1];
+	int rc;
+
+	sw->fd = open(fname, O_RDONLY);
+	if (sw->fd < 0)
+		return sw->fd;
+
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		sw->stream = fdopen(sw->fd, "r");
+		if (!sw->stream) {
+			perror("fdopen");
+			return -1;
+		}
+		/* read first line and parse file ID and VERSION */
+		rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n",
+			    file_id, file_version);
+		if (rc != 2) {
+			perror("parsing header line of software file");
+			return -1;
+		}
+		strcpy((char *)sw->file_id, file_id);
+		sw->file_id_len = strlen(file_id);
+		strcpy((char *)sw->file_version, file_version);
+		sw->file_version_len = strlen(file_version);
+		/* rewind to start of file */
+		rewind(sw->stream);
+		break;	
+	case GSM_BTS_TYPE_NANOBTS:
+		/* TODO: extract that from the filename or content */
+		rc = parse_sdp_header(sw);
+		if (rc < 0) {
+			fprintf(stderr, "Could not parse the ipaccess SDP header\n");
+			return -1;
+		}
+
+		strcpy((char *)sw->file_id, "id");
+		sw->file_id_len = 3;
+		strcpy((char *)sw->file_version, "version");
+		sw->file_version_len = 8;
+		break;
+	default:
+		/* We don't know how to treat them yet */
+		close(sw->fd);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+	
+static void sw_close_file(struct abis_nm_sw *sw)
+{
+	switch (sw->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		fclose(sw->stream);
+		break;
+	default:
+		close(sw->fd);
+		break;
+	}
+}
+
+/* Fill the window */
+static int sw_fill_window(struct abis_nm_sw *sw)
+{
+	int rc;
+
+	while (sw->seg_in_window < sw->window_size) {
+		rc = sw_load_segment(sw);
+		if (rc < 0)
+			return rc;
+		if (sw->last_seg)
+			break;
+	}
+	return 0;
+}
+
+/* callback function from abis_nm_rcvmsg() handler */
+static int abis_nm_rcvmsg_sw(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	int rc = -1;
+	struct abis_nm_sw *sw = &g_sw;
+	enum sw_state old_state = sw->state;
+	
+	//DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type);
+
+	switch (sw->state) {
+	case SW_STATE_WAIT_INITACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_INIT_ACK:
+			/* fill window with segments */
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_INIT_ACK, mb,
+					 sw->cb_data, NULL);
+			rc = sw_fill_window(sw);
+			sw->state = SW_STATE_WAIT_SEGACK;
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		case NM_MT_LOAD_INIT_NACK:
+			if (sw->forced) {
+				DEBUGP(DNM, "FORCED: Ignoring Software Load "
+					"Init NACK\n");
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_INIT_ACK, mb,
+						 sw->cb_data, NULL);
+				rc = sw_fill_window(sw);
+				sw->state = SW_STATE_WAIT_SEGACK;
+			} else {
+				DEBUGP(DNM, "Software Load Init NACK\n");
+				/* FIXME: cause */
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_INIT_NACK, mb,
+						 sw->cb_data, NULL);
+				sw->state = SW_STATE_ERROR;
+			}
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		}
+		break;
+	case SW_STATE_WAIT_SEGACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_SEG_ACK:
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_SEG_ACK, mb,
+					 sw->cb_data, NULL);
+			sw->seg_in_window = 0;
+			if (!sw->last_seg) {
+				/* fill window with more segments */
+				rc = sw_fill_window(sw);
+				sw->state = SW_STATE_WAIT_SEGACK;
+			} else {
+				/* end the transfer */
+				sw->state = SW_STATE_WAIT_ENDACK;
+				rc = sw_load_end(sw);
+			}
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		case NM_MT_LOAD_ABORT:
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_ABORT, mb,
+					 sw->cb_data, NULL);
+			break;
+		}
+		break;
+	case SW_STATE_WAIT_ENDACK:
+		switch (foh->msg_type) {
+		case NM_MT_LOAD_END_ACK:
+			sw_close_file(sw);
+			DEBUGP(DNM, "Software Load End (BTS %u)\n",
+				sw->bts->nr);
+			sw->state = SW_STATE_NONE;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_LOAD_END_ACK, mb,
+					 sw->cb_data, NULL);
+			rc = 0;
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		case NM_MT_LOAD_END_NACK:
+			if (sw->forced) {
+				DEBUGP(DNM, "FORCED: Ignoring Software Load"
+					"End NACK\n");
+				sw->state = SW_STATE_NONE;
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_END_ACK, mb,
+						 sw->cb_data, NULL);
+			} else {
+				DEBUGP(DNM, "Software Load End NACK\n");
+				/* FIXME: cause */
+				sw->state = SW_STATE_ERROR;
+				if (sw->cbfn)
+					sw->cbfn(GSM_HOOK_NM_SWLOAD,
+						 NM_MT_LOAD_END_NACK, mb,
+						 sw->cb_data, NULL);
+			}
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		}
+	case SW_STATE_WAIT_ACTACK:
+		switch (foh->msg_type) {
+		case NM_MT_ACTIVATE_SW_ACK:
+			/* we're done */
+			DEBUGP(DNM, "Activate Software DONE!\n");
+			sw->state = SW_STATE_NONE;
+			rc = 0;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_ACTIVATE_SW_ACK, mb,
+					 sw->cb_data, NULL);
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		case NM_MT_ACTIVATE_SW_NACK:
+			DEBUGP(DNM, "Activate Software NACK\n");
+			/* FIXME: cause */
+			sw->state = SW_STATE_ERROR;
+			if (sw->cbfn)
+				sw->cbfn(GSM_HOOK_NM_SWLOAD,
+					 NM_MT_ACTIVATE_SW_NACK, mb,
+					 sw->cb_data, NULL);
+			abis_nm_queue_send_next(mb->trx->bts);
+			break;
+		}
+	case SW_STATE_NONE:
+		switch (foh->msg_type) {
+		case NM_MT_ACTIVATE_SW_ACK:
+			rc = 0;
+			break;
+		}
+		break;
+	case SW_STATE_ERROR:
+		break;
+	}
+
+	if (rc)
+		DEBUGP(DNM, "unexpected NM MT 0x%02x in state %u -> %u\n",
+			foh->msg_type, old_state, sw->state);
+
+	return rc;
+}
+
+/* Load the specified software into the BTS */
+int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
+			  u_int8_t win_size, int forced,
+			  gsm_cbfn *cbfn, void *cb_data)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	int rc;
+
+	DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n",
+		bts->nr, fname);
+
+	if (sw->state != SW_STATE_NONE)
+		return -EBUSY;
+
+	sw->bts = bts;
+	sw->trx_nr = trx_nr;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		sw->obj_class = NM_OC_SITE_MANAGER;
+		sw->obj_instance[0] = 0xff;
+		sw->obj_instance[1] = 0xff;
+		sw->obj_instance[2] = 0xff;
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		sw->obj_class = NM_OC_BASEB_TRANSC;
+		sw->obj_instance[0] = sw->bts->nr;
+		sw->obj_instance[1] = sw->trx_nr;
+		sw->obj_instance[2] = 0xff;
+		break;
+	case GSM_BTS_TYPE_UNKNOWN:
+	default:
+		LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
+		return -1;
+		break;
+	}
+	sw->window_size = win_size;
+	sw->state = SW_STATE_WAIT_INITACK;
+	sw->cbfn = cbfn;
+	sw->cb_data = cb_data;
+	sw->forced = forced;
+
+	rc = sw_open_file(sw, fname);
+	if (rc < 0) {
+		sw->state = SW_STATE_NONE;
+		return rc;
+	}
+
+	return sw_load_init(sw);
+}
+
+int abis_nm_software_load_status(struct gsm_bts *bts)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	struct stat st;
+	int rc, percent;
+
+	rc = fstat(sw->fd, &st);
+	if (rc < 0) {
+		perror("ERROR during stat");
+		return rc;
+	}
+
+	if (sw->stream)
+		percent = (ftell(sw->stream) * 100) / st.st_size;
+	else
+		percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size;
+	return percent;
+}
+
+/* Activate the specified software into the BTS */
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+			      gsm_cbfn *cbfn, void *cb_data)
+{
+	struct abis_nm_sw *sw = &g_sw;
+	int rc;
+
+	DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n",
+		bts->nr, fname);
+
+	if (sw->state != SW_STATE_NONE)
+		return -EBUSY;
+
+	sw->bts = bts;
+	sw->obj_class = NM_OC_SITE_MANAGER;
+	sw->obj_instance[0] = 0xff;
+	sw->obj_instance[1] = 0xff;
+	sw->obj_instance[2] = 0xff;
+	sw->state = SW_STATE_WAIT_ACTACK;
+	sw->cbfn = cbfn;
+	sw->cb_data = cb_data;
+
+	/* Open the file in order to fill some sw struct members */
+	rc = sw_open_file(sw, fname);
+	if (rc < 0) {
+		sw->state = SW_STATE_NONE;
+		return rc;
+	}
+	sw_close_file(sw);
+
+	return sw_activate(sw);
+}
+
+static void fill_nm_channel(struct abis_nm_channel *ch, u_int8_t bts_port,
+		       u_int8_t ts_nr, u_int8_t subslot_nr)
+{
+	ch->attrib = NM_ATT_ABIS_CHANNEL;
+	ch->bts_port = bts_port;
+	ch->timeslot = ts_nr;
+	ch->subslot = subslot_nr;	
+}
+
+int abis_nm_establish_tei(struct gsm_bts *bts, u_int8_t trx_nr,
+			  u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	u_int8_t len = sizeof(*ch) + 2;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER,
+			bts->bts_nr, trx_nr, 0xff);
+	
+	msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+			   u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot)
+{
+	struct gsm_bts *bts = trx->bts;
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN,
+			NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff);
+	
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+			   struct abis_nm_abis_channel *chan)
+{
+}
+#endif
+
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+			   u_int8_t e1_port, u_int8_t e1_timeslot,
+			   u_int8_t e1_subslot)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF,
+			NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+	DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n",
+		gsm_ts_name(ts),
+		e1_port, e1_timeslot, e1_subslot);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+			   struct abis_nm_abis_channel *chan,
+			   u_int8_t subchan)
+{
+}
+#endif
+
+/* Chapter 8.6.1 */
+int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *cur;
+
+	DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.6.2 */
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, u_int8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *cur;
+
+	DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER,
+			trx->bts->bts_nr, trx->nr, 0xff);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+static int verify_chan_comb(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+	int i;
+
+	/* As it turns out, the BS-11 has some very peculiar restrictions
+	 * on the channel combinations it allows */
+	switch (ts->trx->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		switch (chan_comb) {
+		case NM_CHANC_TCHHalf:
+		case NM_CHANC_TCHHalf2:
+			/* not supported */
+			return -EINVAL;
+		case NM_CHANC_SDCCH:
+			/* only one SDCCH/8 per TRX */
+			for (i = 0; i < TRX_NR_TS; i++) {
+				if (i == ts->nr)
+					continue;
+				if (ts->trx->ts[i].nm_chan_comb ==
+				    NM_CHANC_SDCCH)
+					return -EINVAL;
+			}
+			/* not allowed for TS0 of BCCH-TRX */
+			if (ts->trx == ts->trx->bts->c0 &&
+			    ts->nr == 0)
+					return -EINVAL;
+			/* not on the same TRX that has a BCCH+SDCCH4
+			 * combination */
+			if (ts->trx == ts->trx->bts->c0 &&
+			    (ts->trx->ts[0].nm_chan_comb == 5 ||
+			     ts->trx->ts[0].nm_chan_comb == 8))
+					return -EINVAL;
+			break;
+		case NM_CHANC_mainBCCH:
+		case NM_CHANC_BCCHComb:
+			/* allowed only for TS0 of C0 */
+			if (ts->trx != ts->trx->bts->c0 ||
+			    ts->nr != 0)
+				return -EINVAL;
+			break;
+		case NM_CHANC_BCCH:
+			/* allowed only for TS 2/4/6 of C0 */
+			if (ts->trx != ts->trx->bts->c0)
+				return -EINVAL;
+			if (ts->nr != 2 && ts->nr != 4 &&
+			    ts->nr != 6)
+				return -EINVAL;
+			break;
+		case 8: /* this is not like 08.58, but in fact
+			 * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */
+			/* FIXME: only one CBCH allowed per cell */
+			break;
+		}
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		switch (ts->nr) {
+		case 0:
+			if (ts->trx->nr == 0) {
+				/* only on TRX0 */
+				switch (chan_comb) {
+				case NM_CHANC_BCCH:
+				case NM_CHANC_mainBCCH:
+				case NM_CHANC_BCCHComb:
+					return 0;
+					break;
+				default:
+					return -EINVAL;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					return -EINVAL;
+				}
+			}
+			break;
+		case 1:
+			if (ts->trx->nr == 0) {
+				switch (chan_comb) {
+				case NM_CHANC_SDCCH_CBCH:
+					if (ts->trx->ts[0].nm_chan_comb ==
+					    NM_CHANC_mainBCCH)
+						return 0;
+					return -EINVAL;
+				case NM_CHANC_SDCCH:
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_PDCH:
+					return 0;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_SDCCH:
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					return -EINVAL;
+				}
+			}
+			break;
+		case 2:
+		case 3:
+		case 4:
+		case 5:
+		case 6:
+		case 7:
+			switch (chan_comb) {
+			case NM_CHANC_TCHFull:
+			case NM_CHANC_TCHHalf:
+			case NM_CHANC_IPAC_TCHFull_TCHHalf:
+				return 0;
+			case NM_CHANC_IPAC_PDCH:
+			case NM_CHANC_IPAC_TCHFull_PDCH:
+				if (ts->trx->nr == 0)
+					return 0;
+				else
+					return -EINVAL;
+			}
+			break;
+		}
+		return -EINVAL;
+	default:
+		/* unknown BTS type */
+		return 0;
+	}
+	return 0;
+}
+
+/* Chapter 8.6.3 */
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	u_int8_t zero = 0x00;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2 + 2;
+
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		len += 4 + 2 + 2 + 3;
+
+	DEBUGP(DNM, "Set Chan Attr %s\n", gsm_ts_name(ts));
+	if (verify_chan_comb(ts, chan_comb) < 0) {
+		msgb_free(msg);
+		DEBUGP(DNM, "Invalid Channel Combination!!!\n");
+		return -EINVAL;
+	}
+	ts->nm_chan_comb = chan_comb;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR,
+			NM_OC_CHANNEL, bts->bts_nr,
+			ts->trx->nr, ts->nr);
+	msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb);
+	if (ts->hopping.enabled) {
+		unsigned int i;
+		uint8_t *len;
+
+		msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn);
+		msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio);
+
+		/* build the ARFCN list */
+		msgb_put_u8(msg, NM_ATT_ARFCN_LIST);
+		len = msgb_put(msg, 1);
+		*len = 0;
+		for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+			if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) {
+				msgb_put_u16(msg, i);
+				/* At least BS-11 wants a TLV16 here */
+				if (bts->type == GSM_BTS_TYPE_BS11)
+					*len += 1;
+				else
+					*len += sizeof(uint16_t);
+			}
+		}
+	}
+	msgb_tv_put(msg, NM_ATT_TSC, bts->tsc);	/* training sequence */
+	if (bts->type == GSM_BTS_TYPE_BS11)
+		msgb_tlv_put(msg, 0x59, 1, &zero);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i1,
+			u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t msgtype = NM_MT_SW_ACT_REQ_ACK;
+	u_int8_t len = att_len;
+
+	if (nack) {
+		len += 2;
+		msgtype = NM_MT_SW_ACT_REQ_NACK;
+	}
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3);
+
+	if (attr) {
+		u_int8_t *ptr = msgb_put(msg, att_len);
+		memcpy(ptr, attr, att_len);
+	}
+	if (nack)
+		msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP);
+
+	return abis_nm_sendmsg_direct(bts, msg);
+}
+
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, u_int8_t *rawmsg)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	u_int8_t *data;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+	fill_om_hdr(oh, len);
+	data = msgb_put(msg, len);
+	memcpy(data, rawmsg, len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Siemens specific commands */
+static int __simple_cmd(struct gsm_bts *bts, u_int8_t msg_type)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.9.2 */
+int abis_nm_opstart(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2);
+
+	debugp_foh((struct abis_om_fom_hdr *) oh->data);
+	DEBUGPC(DNM, "Sending OPSTART\n");
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.8.5 */
+int abis_nm_chg_adm_state(struct gsm_bts *bts, u_int8_t obj_class, u_int8_t i0,
+			  u_int8_t i1, u_int8_t i2, enum abis_nm_adm_state adm_state)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2);
+	msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, u_int8_t e1_port0, u_int8_t ts0,
+			    u_int8_t e1_port1, u_int8_t ts1)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *attr;
+
+	DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n",
+		e1_port0, ts0, e1_port1, ts1);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK,
+			NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00);
+
+	attr = msgb_put(msg, 3);
+	attr[0] = NM_ATT_MDROP_LINK;
+	attr[1] = e1_port0;
+	attr[2] = ts0;
+
+	attr = msgb_put(msg, 3);
+	attr[0] = NM_ATT_MDROP_NEXT;
+	attr[1] = e1_port1;
+	attr[2] = ts1;
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.7.1 */
+int abis_nm_perform_test(struct gsm_bts *bts, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report, struct msgb *msg)
+{
+	struct abis_om_hdr *oh;
+
+	DEBUGP(DNM, "PEFORM TEST %s\n", get_value_string(test_names, test_nr));
+
+	if (!msg)
+		msg = nm_msgb_alloc();
+
+	msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report);
+	msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr);
+	oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST,
+			obj_class, bts_nr, trx_nr, ts_nr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_event_reports(struct gsm_bts *bts, int on)
+{
+	if (on == 0)
+		return __simple_cmd(bts, NM_MT_STOP_EVENT_REP);
+	else
+		return __simple_cmd(bts, NM_MT_REST_EVENT_REP);
+}
+
+/* Siemens (or BS-11) specific commands */
+
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect)
+{
+	if (reconnect == 0)
+		return __simple_cmd(bts, NM_MT_BS11_DISCONNECT);
+	else
+		return __simple_cmd(bts, NM_MT_BS11_RECONNECT);
+}
+
+int abis_nm_bs11_restart(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_RESTART);
+}
+
+
+struct bs11_date_time {
+	u_int16_t	year;
+	u_int8_t	month;
+	u_int8_t	day;
+	u_int8_t	hour;
+	u_int8_t	min;
+	u_int8_t	sec;
+} __attribute__((packed));
+
+
+void get_bs11_date_time(struct bs11_date_time *aet)
+{
+	time_t t;
+	struct tm *tm;
+
+	t = time(NULL);
+	tm = localtime(&t);
+	aet->sec = tm->tm_sec;
+	aet->min = tm->tm_min;
+	aet->hour = tm->tm_hour;
+	aet->day = tm->tm_mday;
+	aet->month = tm->tm_mon;
+	aet->year = htons(1900 + tm->tm_year);
+}
+
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE);
+}
+
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin)
+{
+	if (begin)
+		return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX);
+	else
+		return __simple_cmd(bts, NM_MT_BS11_END_DB_TX);
+}
+
+int abis_nm_bs11_create_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, u_int8_t idx,
+				u_int8_t attr_len, const u_int8_t *attr)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t *cur;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ,
+			NM_OC_BS11, type, 0, idx);
+	cur = msgb_put(msg, attr_len);
+	memcpy(cur, attr, attr_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+				enum abis_bs11_objtype type, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ,
+			NM_OC_BS11, type, 0, idx);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t zero = 0x00;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ,
+			NM_OC_BS11_ENVABTSE, 0, idx, 0xff);
+	msgb_tlv_put(msg, 0x99, 1, &zero);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT,
+			idx, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT,
+			idx, 0xff, 0xff);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+static const u_int8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL };
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* like abis_nm_conn_terr_traf + set_tei */
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, u_int8_t e1_port,
+			  u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR,
+			NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+
+	ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+	fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+	msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, u_int8_t level)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR,
+			NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+	msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr = NM_ATT_BS11_TXPWR;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr[] = { NM_ATT_BS11_PLL_MODE };
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY,
+			    NM_ATT_BS11_CCLK_TYPE };
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+	return abis_nm_sendmsg(bts, msg);
+
+}
+
+//static const u_int8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 };
+
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on)
+{
+	return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on);
+}
+
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on)
+{
+	return abis_nm_bs11_logon(bts, 0x03, "FIELD  ", on);
+}
+
+int abis_nm_bs11_logon(struct gsm_bts *bts, u_int8_t level, const char *name, int on)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time bdt;
+
+	get_bs11_date_time(&bdt);
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	if (on) {
+		u_int8_t len = 3*2 + sizeof(bdt)
+				+ 1 + strlen(name);
+		fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON,
+				NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME,
+			     sizeof(bdt), (u_int8_t *) &bdt);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV,
+			     1, &level);
+		msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME,
+			     strlen(name), (u_int8_t *)name);
+	} else {
+		fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF,
+				NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+	}
+	
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+
+	if (strlen(password) != 10)
+		return -EINVAL;
+
+ 	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR,
+			NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const u_int8_t *)password);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+	u_int8_t tlv_value;
+	
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+			BS11_OBJ_LI, 0x00, 0x00);
+
+	if (locked)
+		tlv_value = BS11_LI_PLL_LOCKED;
+	else
+		tlv_value = BS11_LI_PLL_STANDALONE;
+	
+	msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value);
+	
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* Set the calibration value of the PLL (work value/set value)
+ * It depends on the login which one is changed */
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg;
+	u_int8_t tlv_value[2];
+
+	msg = nm_msgb_alloc();
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+			BS11_OBJ_TRX1, 0x00, 0x00);
+
+	tlv_value[0] = value>>8;
+	tlv_value[1] = value&0xff;
+
+	msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_state(struct gsm_bts *bts)
+{
+	return __simple_cmd(bts, NM_MT_BS11_GET_STATE);
+}
+
+/* BS11 SWL */
+
+void *tall_fle_ctx;
+
+struct abis_nm_bs11_sw {
+	struct gsm_bts *bts;
+	char swl_fname[PATH_MAX];
+	u_int8_t win_size;
+	int forced;
+	struct llist_head file_list;
+	gsm_cbfn *user_cb;	/* specified by the user */
+};
+static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw;
+
+struct file_list_entry {
+	struct llist_head list;
+	char fname[PATH_MAX];
+};
+
+struct file_list_entry *fl_dequeue(struct llist_head *queue)
+{
+	struct llist_head *lh;
+
+	if (llist_empty(queue))
+		return NULL;
+
+	lh = queue->next;
+	llist_del(lh);
+	
+	return llist_entry(lh, struct file_list_entry, list);
+}
+
+static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw)
+{
+	char linebuf[255];
+	struct llist_head *lh, *lh2;
+	FILE *swl;
+	int rc = 0;
+
+	swl = fopen(bs11_sw->swl_fname, "r");
+	if (!swl)
+		return -ENODEV;
+
+	/* zero the stale file list, if any */
+	llist_for_each_safe(lh, lh2, &bs11_sw->file_list) {
+		llist_del(lh);
+		talloc_free(lh);
+	}
+
+	while (fgets(linebuf, sizeof(linebuf), swl)) {
+		char file_id[12+1];
+		char file_version[80+1];
+		struct file_list_entry *fle;
+		static char dir[PATH_MAX];
+
+		if (strlen(linebuf) < 4)
+			continue;
+	
+		rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version);
+		if (rc < 0) {
+			perror("ERR parsing SWL file");
+			rc = -EINVAL;
+			goto out;
+		}
+		if (rc < 2)
+			continue;
+
+		fle = talloc_zero(tall_fle_ctx, struct file_list_entry);
+		if (!fle) {
+			rc = -ENOMEM;
+			goto out;
+		}
+
+		/* construct new filename */
+		strncpy(dir, bs11_sw->swl_fname, sizeof(dir));
+		strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1);
+		strcat(fle->fname, "/");
+		strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname));
+		
+		llist_add_tail(&fle->list, &bs11_sw->file_list);
+	}
+
+out:
+	fclose(swl);
+	return rc;
+}
+
+/* bs11 swload specific callback, passed to abis_nm core swload */
+static int bs11_swload_cbfn(unsigned int hook, unsigned int event,
+			    struct msgb *msg, void *data, void *param)
+{
+	struct abis_nm_bs11_sw *bs11_sw = data;
+	struct file_list_entry *fle;
+	int rc = 0;
+
+	switch (event) {
+	case NM_MT_LOAD_END_ACK:
+		fle = fl_dequeue(&bs11_sw->file_list);
+		if (fle) {
+			/* start download the next file of our file list */
+			rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname,
+						   bs11_sw->win_size,
+						   bs11_sw->forced,
+						   &bs11_swload_cbfn, bs11_sw);
+			talloc_free(fle);
+		} else {
+			/* activate the SWL */
+			rc = abis_nm_software_activate(bs11_sw->bts,
+							bs11_sw->swl_fname,
+							bs11_swload_cbfn,
+							bs11_sw);
+		}
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+	case NM_MT_LOAD_END_NACK:
+	case NM_MT_LOAD_INIT_ACK:
+	case NM_MT_LOAD_INIT_NACK:
+	case NM_MT_ACTIVATE_SW_NACK:
+	case NM_MT_ACTIVATE_SW_ACK:
+	default:
+		/* fallthrough to the user callback */
+		if (bs11_sw->user_cb)
+			rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL);
+		break;
+	}
+
+	return rc;
+}
+
+/* Siemens provides a SWL file that is a mere listing of all the other
+ * files that are part of a software release.  We need to upload first
+ * the list file, and then each file that is listed in the list file */
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+			  u_int8_t win_size, int forced, gsm_cbfn *cbfn)
+{
+	struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw;
+	struct file_list_entry *fle;
+	int rc = 0;
+
+	INIT_LLIST_HEAD(&bs11_sw->file_list);
+	bs11_sw->bts = bts;
+	bs11_sw->win_size = win_size;
+	bs11_sw->user_cb = cbfn;
+	bs11_sw->forced = forced;
+
+	strncpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname));
+	rc = bs11_read_swl_file(bs11_sw);
+	if (rc < 0)
+		return rc;
+
+	/* dequeue next item in file list */
+	fle = fl_dequeue(&bs11_sw->file_list);
+	if (!fle)
+		return -EINVAL;
+
+	/* start download the next file of our file list */
+	rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced,
+				   bs11_swload_cbfn, bs11_sw);
+	talloc_free(fle);
+	return rc;
+}
+
+#if 0
+static u_int8_t req_attr_btse[] = {
+	NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION,
+	NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV,
+	NM_ATT_BS11_LMT_USER_NAME,
+
+	0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME,
+
+	NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY,
+
+	NM_ATT_BS11_SW_LOAD_STORED };
+
+static u_int8_t req_attr_btsm[] = {
+	NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME,
+	NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID,
+	NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG,
+	NM_ATT_SW_DESCR, NM_ATT_GET_ARI };
+#endif
+	
+static u_int8_t req_attr[] = {
+	NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE,
+	0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO,
+	0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL };
+
+int abis_nm_bs11_get_serno(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	/* SiemensHW CCTRL object */
+	fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11,
+			0x03, 0x00, 0x00);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time aet;
+
+	get_bs11_date_time(&aet);
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	/* SiemensHW CCTRL object */
+	fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER,
+			0xff, 0xff, 0xff);
+	msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (u_int8_t *) &aet);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t attr = NM_ATT_BS11_LINE_CFG;
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+			NM_OC_BS11_BPORT, bport, 0xff, 0x02);
+	msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport, enum abis_bs11_line_cfg line_cfg)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	struct bs11_date_time aet;
+
+	get_bs11_date_time(&aet);
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT,
+			bport, 0xff, 0x02);
+	msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* ip.access nanoBTS specific commands */
+static const char ipaccess_magic[] = "com.ipaccess";
+
+
+static int abis_nm_rx_ipacc(struct msgb *msg)
+{
+	struct in_addr addr;
+	struct abis_om_hdr *oh = msgb_l2(msg);
+	struct abis_om_fom_hdr *foh;
+	u_int8_t idstrlen = oh->data[0];
+	struct tlv_parsed tp;
+	struct ipacc_ack_signal_data signal;
+
+	if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) {
+		LOGP(DNM, LOGL_ERROR, "id string is not com.ipaccess !?!\n");
+		return -EINVAL;
+	}
+
+	foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen);
+	abis_nm_tlv_parse(&tp, msg->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	debugp_foh(foh);
+
+	DEBUGPC(DNM, "IPACCESS(0x%02x): ", foh->msg_type);
+
+	switch (foh->msg_type) {
+	case NM_MT_IPACC_RSL_CONNECT_ACK:
+		DEBUGPC(DNM, "RSL CONNECT ACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) {
+			memcpy(&addr,
+				TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr));
+
+			DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr));
+		}
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT))
+			DEBUGPC(DNM, "PORT=%u ",
+				ntohs(*((u_int16_t *)
+					TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT))));
+		if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID))
+			DEBUGPC(DNM, "STREAM=0x%02x ",
+					*TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID));
+		DEBUGPC(DNM, "\n");
+		break;
+	case NM_MT_IPACC_RSL_CONNECT_NACK:
+		LOGP(DNM, LOGL_ERROR, "RSL CONNECT NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			DEBUGPC(DNM, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\n");
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		DEBUGPC(DNM, "SET NVATTR ACK\n");
+		/* FIXME: decode and show the actual attributes */
+		break;
+	case NM_MT_IPACC_SET_NVATTR_NACK:
+		LOGP(DNM, LOGL_ERROR, "SET NVATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_GET_NVATTR_ACK:
+		DEBUGPC(DNM, "GET NVATTR ACK\n");
+		/* FIXME: decode and show the actual attributes */
+		break;
+	case NM_MT_IPACC_GET_NVATTR_NACK:
+		LOGPC(DNM, LOGL_ERROR, "GET NVATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	case NM_MT_IPACC_SET_ATTR_ACK:
+		DEBUGPC(DNM, "SET ATTR ACK\n");
+		break;
+	case NM_MT_IPACC_SET_ATTR_NACK:
+		LOGPC(DNM, LOGL_ERROR, "SET ATTR NACK ");
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			LOGPC(DNM, LOGL_ERROR, "\n");
+		break;
+	default:
+		DEBUGPC(DNM, "unknown\n");
+		break;
+	}
+
+	/* signal handling */
+	switch  (foh->msg_type) {
+	case NM_MT_IPACC_RSL_CONNECT_NACK:
+	case NM_MT_IPACC_SET_NVATTR_NACK:
+	case NM_MT_IPACC_GET_NVATTR_NACK:
+		signal.trx = gsm_bts_trx_by_nr(msg->trx->bts, foh->obj_inst.trx_nr);
+		signal.msg_type = foh->msg_type;
+		dispatch_signal(SS_NM, S_NM_IPACC_NACK, &signal);
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		signal.trx = gsm_bts_trx_by_nr(msg->trx->bts, foh->obj_inst.trx_nr);
+		signal.msg_type = foh->msg_type;
+		dispatch_signal(SS_NM, S_NM_IPACC_ACK, &signal);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* send an ip-access manufacturer specific message */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, u_int8_t msg_type,
+			 u_int8_t obj_class, u_int8_t bts_nr,
+			 u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t *attr, int attr_len)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	u_int8_t *data;
+
+	/* construct the 12.21 OM header, observe the erroneous length */
+	oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+	fill_om_hdr(oh, sizeof(*foh) + attr_len);
+	oh->mdisc = ABIS_OM_MDISC_MANUF;
+
+	/* add the ip.access magic */
+	data = msgb_put(msg, sizeof(ipaccess_magic)+1);
+	*data++ = sizeof(ipaccess_magic);
+	memcpy(data, ipaccess_magic, sizeof(ipaccess_magic));
+
+	/* fill the 12.21 FOM header */
+	foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh));
+	foh->msg_type = msg_type;
+	foh->obj_class = obj_class;
+	foh->obj_inst.bts_nr = bts_nr;
+	foh->obj_inst.trx_nr = trx_nr;
+	foh->obj_inst.ts_nr = ts_nr;
+
+	if (attr && attr_len) {
+		data = msgb_put(msg, attr_len);
+		memcpy(data, attr, attr_len);
+	}
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+/* set some attributes in NVRAM */
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, u_int8_t *attr,
+				int attr_len)
+{
+	return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR,
+				    NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr,
+				    attr_len);
+}
+
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx,
+				 u_int32_t ip, u_int16_t port, u_int8_t stream)
+{
+	struct in_addr ia;
+	u_int8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0,
+			    NM_ATT_IPACC_DST_IP_PORT, 0, 0,
+			    NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 };
+
+	int attr_len = sizeof(attr);
+
+	ia.s_addr = htonl(ip);
+	attr[1] = stream;
+	attr[3] = port >> 8;
+	attr[4] = port & 0xff;
+	*(u_int32_t *)(attr+6) = ia.s_addr;
+
+	/* if ip == 0, we use the default IP */
+	if (ip == 0)
+		attr_len -= 5;
+
+	DEBUGP(DNM, "ip.access RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+		inet_ntoa(ia), port, stream);
+
+	return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT,
+				    NM_OC_BASEB_TRANSC, trx->bts->bts_nr,
+				    trx->nr, 0xff, attr, attr_len);
+}
+
+/* restart / reboot an ip.access nanoBTS */
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+
+	oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+	fill_om_fom_hdr(oh, 0, NM_MT_IPACC_RESTART, NM_OC_BASEB_TRANSC,
+			trx->bts->nr, trx->nr, 0xff);
+
+	return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class,
+				u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+				u_int8_t *attr, u_int8_t attr_len)
+{
+	return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR,
+				    obj_class, bts_nr, trx_nr, ts_nr,
+				     attr, attr_len);
+}
+
+void abis_nm_ipaccess_cgi(u_int8_t *buf, struct gsm_bts *bts)
+{
+	/* we simply reuse the GSM48 function and overwrite the RAC
+	 * with the Cell ID */
+	gsm48_ra_id_by_bts(buf, bts);
+	*((u_int16_t *)(buf + 5)) = htons(bts->cell_identity);
+}
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked)
+{
+	int new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+
+	trx->nm_state.administrative = new_state;
+	if (!trx->bts || !trx->bts->oml_link)
+		return;
+
+	abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
+			      trx->bts->bts_nr, trx->nr, 0xff,
+			      new_state);
+}
+
+static const struct value_string ipacc_testres_names[] = {
+	{ NM_IPACC_TESTRES_SUCCESS,	"SUCCESS" },
+	{ NM_IPACC_TESTRES_TIMEOUT,	"TIMEOUT" },
+	{ NM_IPACC_TESTRES_NO_CHANS,	"NO CHANNELS" },
+	{ NM_IPACC_TESTRES_PARTIAL,	"PARTIAL" },
+	{ NM_IPACC_TESTRES_STOPPED,	"STOPPED" },
+	{ 0,				NULL }
+};
+
+const char *ipacc_testres_name(u_int8_t res)
+{
+	return get_value_string(ipacc_testres_names, res);
+}
+
+void ipac_parse_cgi(struct cell_global_id *cid, const u_int8_t *buf)
+{
+	cid->mcc = (buf[0] & 0xf) * 100;
+	cid->mcc += (buf[0] >> 4) *  10;
+	cid->mcc += (buf[1] & 0xf) *  1;
+
+	if (buf[1] >> 4 == 0xf) {
+		cid->mnc = (buf[2] & 0xf) * 10;
+		cid->mnc += (buf[2] >> 4) *  1;
+	} else {
+		cid->mnc = (buf[2] & 0xf) * 100;
+		cid->mnc += (buf[2] >> 4) *  10;
+		cid->mnc += (buf[1] >> 4) *   1;
+	}
+
+	cid->lac = ntohs(*((u_int16_t *)&buf[3]));
+	cid->ci = ntohs(*((u_int16_t *)&buf[5]));
+}
+
+/* parse BCCH information IEI from wire format to struct ipac_bcch_info */
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, u_int8_t *buf)
+{
+	u_int8_t *cur = buf;
+	u_int16_t len;
+
+	memset(binf, 0, sizeof(*binf));
+
+	if (cur[0] != NM_IPAC_EIE_BCCH_INFO)
+		return -EINVAL;
+	cur++;
+
+	len = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	binf->info_type = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+		binf->freq_qual = *cur >> 2;
+
+	binf->arfcn = (*cur++ & 3) << 8;
+	binf->arfcn |= *cur++;
+
+	if (binf->info_type & IPAC_BINF_RXLEV)
+		binf->rx_lev = *cur & 0x3f;
+	cur++;
+
+	if (binf->info_type & IPAC_BINF_RXQUAL)
+		binf->rx_qual = *cur & 0x7;
+	cur++;
+
+	if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+		binf->freq_err = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_OFFSET)
+		binf->frame_offset = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET)
+		binf->frame_nr_offset = ntohl(*(u_int32_t *)cur);
+	cur += 4;
+
+#if 0
+	/* Somehow this is not set correctly */
+	if (binf->info_type & IPAC_BINF_BSIC)
+#endif
+		binf->bsic = *cur & 0x3f;
+	cur++;
+
+	ipac_parse_cgi(&binf->cgi, cur);
+	cur += 7;
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) {
+		memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2));
+		cur += sizeof(binf->ba_list_si2);
+	}
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+		memcpy(binf->ba_list_si2bis, cur,
+			sizeof(binf->ba_list_si2bis));
+		cur += sizeof(binf->ba_list_si2bis);
+	}
+
+	if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+		memcpy(binf->ba_list_si2ter, cur,
+			sizeof(binf->ba_list_si2ter));
+		cur += sizeof(binf->ba_list_si2ter);
+	}
+
+	return 0;
+}
+
+void abis_nm_clear_queue(struct gsm_bts *bts)
+{
+	struct msgb *msg;
+
+	while (!llist_empty(&bts->abis_queue)) {
+		msg = msgb_dequeue(&bts->abis_queue);
+		msgb_free(msg);
+	}
+
+	bts->abis_nm_pend = 0;
+}
diff --git a/src/libbsc/abis_nm_vty.c b/src/libbsc/abis_nm_vty.c
new file mode 100644
index 0000000..996a857
--- /dev/null
+++ b/src/libbsc/abis_nm_vty.c
@@ -0,0 +1,197 @@
+/* VTY interface for A-bis OML (Netowrk Management) */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct cmd_node oml_node = {
+	OML_NODE,
+	"%s(oml)# ",
+	1,
+};
+
+struct oml_node_state {
+	struct gsm_bts *bts;
+	uint8_t obj_class;
+	uint8_t obj_inst[3];
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+/* FIXME: auto-generate those strings from the value_string lists */
+#define NM_OBJCLASS_VTY "(site-manager|bts|radio-carrier|baseband-transceiver|channel|adjc|handover|power-contorl|btse|rack|test|envabtse|bport|gprs-nse|gprs-cell|gprs-nsvc|siemenshw)"
+#define NM_OBJCLASS_VTY_HELP "FIXME"
+
+DEFUN(oml_class_inst, oml_class_inst_cmd,
+	"bts <0-255> oml class " NM_OBJCLASS_VTY
+					" instance <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OML managed objects\n"
+	"Object Class\n" 	NM_OBJCLASS_VTY_HELP
+	"Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(bsc_gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]);
+	oms->obj_inst[0] = atoi(argv[2]);
+	oms->obj_inst[1] = atoi(argv[3]);
+	oms->obj_inst[2] = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OML_NODE;
+
+	return CMD_SUCCESS;
+
+}
+
+DEFUN(oml_classnum_inst, oml_classnum_inst_cmd,
+	"bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OML managed objects\n"
+	"Object Class\n" "Object Class\n"
+	"Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(bsc_gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->obj_class = atoi(argv[1]);
+	oms->obj_inst[0] = atoi(argv[2]);
+	oms->obj_inst[1] = atoi(argv[3]);
+	oms->obj_inst[2] = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OML_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_attrib_get, oml_attrib_get_cmd,
+	"attribute get <0-255>",
+	"OML Attribute Actions\n" "Get a single OML Attribute\n"
+	"OML Attribute Number\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	/* FIXME */
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_attrib_set, oml_attrib_set_cmd,
+	"attribute set <0-255> .HEX",
+	"OML Attribute Actions\n" "Set a single OML Attribute\n"
+	"OML Attribute Number\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	/* FIXME */
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd,
+	"change-adm-state (locked|unlocked|shutdown|null)",
+	"Change the Administrative State\n"
+	"Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n")
+{
+	struct oml_node_state *oms = vty->index;
+	enum abis_nm_adm_state state;
+
+	state = get_string_value(abis_nm_adm_state_names, argv[0]);
+
+	abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0],
+			      oms->obj_inst[1], oms->obj_inst[2], state);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_opstart, oml_opstart_cmd,
+	"opstart", "Send an OPSTART message to the object")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0],
+			oms->obj_inst[1], oms->obj_inst[2]);
+
+	return CMD_SUCCESS;
+}
+
+int abis_nm_vty_init(void)
+{
+	install_element(ENABLE_NODE, &oml_class_inst_cmd);
+	install_element(ENABLE_NODE, &oml_classnum_inst_cmd);
+	install_node(&oml_node, dummy_config_write);
+
+	install_default(OML_NODE);
+	install_element(OML_NODE, &ournode_exit_cmd);
+	install_element(OML_NODE, &oml_attrib_get_cmd);
+	install_element(OML_NODE, &oml_attrib_set_cmd);
+	install_element(OML_NODE, &oml_chg_adm_state_cmd);
+	install_element(OML_NODE, &oml_opstart_cmd);
+
+	return 0;
+}
diff --git a/src/libbsc/abis_om2000.c b/src/libbsc/abis_om2000.c
new file mode 100644
index 0000000..805b844
--- /dev/null
+++ b/src/libbsc/abis_om2000.c
@@ -0,0 +1,1078 @@
+/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface
+ * implemented based on protocol trace analysis, no formal documentation */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/signal.h>
+#include <openbsc/e1_input.h>
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+
+/* use following functions from abis_nm.c:
+	* om2k_msgb_alloc()
+	* abis_om2k_sendmsg()
+ */
+
+struct abis_om2k_hdr {
+	struct abis_om_hdr om;
+	uint16_t msg_type;
+	struct abis_om2k_mo mo;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+enum abis_om2k_msgtype {
+	OM2K_MSGT_ABORT_SP_CMD			= 0x0000,
+	OM2K_MSGT_ABORT_SP_COMPL		= 0x0002,
+	OM2K_MSGT_ALARM_REP_ACK			= 0x0004,
+	OM2K_MSGT_ALARM_REP_NACK		= 0x0005,
+	OM2K_MSGT_ALARM_REP			= 0x0006,
+	OM2K_MSGT_ALARM_STATUS_REQ		= 0x0008,
+	OM2K_MSGT_ALARM_STATUS_REQ_ACK		= 0x000a,
+	OM2K_MSGT_ALARM_STATUS_REQ_REJ		= 0x000b,
+	OM2K_MSGT_ALARM_STATUS_RES_ACK		= 0x000c,
+	OM2K_MSGT_ALARM_STATUS_RES_NACK		= 0x000d,
+	OM2K_MSGT_ALARM_STATUS_RES		= 0x000e,
+	OM2K_MSGT_CAL_TIME_RESP			= 0x0010,
+	OM2K_MSGT_CAL_TIME_REJ			= 0x0011,
+	OM2K_MSGT_CAL_TIME_REQ			= 0x0012,
+
+	OM2K_MSGT_CON_CONF_REQ			= 0x0014,
+	OM2K_MSGT_CON_CONF_REQ_ACK		= 0x0016,
+	OM2K_MSGT_CON_CONF_REQ_REJ		= 0x0017,
+	OM2K_MSGT_CON_CONF_RES_ACK		= 0x0018,
+	OM2K_MSGT_CON_CONF_RES_NACK		= 0x0019,
+	OM2K_MSGT_CON_CONF_RES			= 0x001a,
+
+	OM2K_MSGT_CONNECT_CMD			= 0x001c,
+	OM2K_MSGT_CONNECT_COMPL			= 0x001e,
+	OM2K_MSGT_CONNECT_REJ			= 0x001f,
+
+	OM2K_MSGT_DISABLE_REQ			= 0x0028,
+	OM2K_MSGT_DISABLE_REQ_ACK		= 0x002a,
+	OM2K_MSGT_DISABLE_REQ_REJ		= 0x002b,
+	OM2K_MSGT_DISABLE_RES_ACK		= 0x002c,
+	OM2K_MSGT_DISABLE_RES_NACK		= 0x002d,
+	OM2K_MSGT_DISABLE_RES			= 0x002e,
+	OM2K_MSGT_DISCONNECT_CMD		= 0x0030,
+	OM2K_MSGT_DISCONNECT_COMPL		= 0x0032,
+	OM2K_MSGT_DISCONNECT_REJ		= 0x0033,
+	OM2K_MSGT_ENABLE_REQ			= 0x0034,
+	OM2K_MSGT_ENABLE_REQ_ACK		= 0x0036,
+	OM2K_MSGT_ENABLE_REQ_REJ		= 0x0037,
+	OM2K_MSGT_ENABLE_RES_ACK		= 0x0038,
+	OM2K_MSGT_ENABLE_RES_NACK		= 0x0039,
+	OM2K_MSGT_ENABLE_RES			= 0x003a,
+
+	OM2K_MSGT_FAULT_REP_ACK			= 0x0040,
+	OM2K_MSGT_FAULT_REP_NACK		= 0x0041,
+	OM2K_MSGT_FAULT_REP			= 0x0042,
+
+	OM2K_MSGT_IS_CONF_REQ			= 0x0060,
+	OM2K_MSGT_IS_CONF_REQ_ACK		= 0x0062,
+	OM2K_MSGT_IS_CONF_REQ_REJ		= 0x0063,
+	OM2K_MSGT_IS_CONF_RES_ACK		= 0x0064,
+	OM2K_MSGT_IS_CONF_RES_NACK		= 0x0065,
+	OM2K_MSGT_IS_CONF_RES			= 0x0066,
+
+	OM2K_MSGT_OP_INFO			= 0x0074,
+	OM2K_MSGT_OP_INFO_ACK			= 0x0076,
+	OM2K_MSGT_OP_INFO_REJ			= 0x0077,
+	OM2K_MSGT_RESET_CMD		 	= 0x0078,
+	OM2K_MSGT_RESET_COMPL			= 0x007a,
+	OM2K_MSGT_RESET_REJ			= 0x007b,
+	OM2K_MSGT_RX_CONF_REQ			= 0x007c,
+	OM2K_MSGT_RX_CONF_REQ_ACK		= 0x007e,
+	OM2K_MSGT_RX_CONF_REQ_REJ		= 0x007f,
+	OM2K_MSGT_RX_CONF_RES_ACK		= 0x0080,
+	OM2K_MSGT_RX_CONF_RES_NACK		= 0x0081,
+	OM2K_MSGT_RX_CONF_RES			= 0x0082,
+	OM2K_MSGT_START_REQ			= 0x0084,
+	OM2K_MSGT_START_REQ_ACK			= 0x0086,
+	OM2K_MSGT_START_REQ_REJ			= 0x0087,
+	OM2K_MSGT_START_RES_ACK			= 0x0088,
+	OM2K_MSGT_START_RES_NACK		= 0x0089,
+	OM2K_MSGT_START_RES			= 0x008a,
+	OM2K_MSGT_STATUS_REQ			= 0x008c,
+	OM2K_MSGT_STATUS_RESP			= 0x008e,
+	OM2K_MSGT_STATUS_REJ			= 0x008f,
+
+	OM2K_MSGT_TEST_REQ			= 0x0094,
+	OM2K_MSGT_TEST_REQ_ACK			= 0x0096,
+	OM2K_MSGT_TEST_REQ_REJ			= 0x0097,
+	OM2K_MSGT_TEST_RES_ACK			= 0x0098,
+	OM2K_MSGT_TEST_RES_NACK			= 0x0099,
+	OM2K_MSGT_TEST_RES			= 0x009a,
+
+	OM2K_MSGT_TF_CONF_REQ			= 0x00a0,
+	OM2K_MSGT_TF_CONF_REQ_ACK		= 0x00a2,
+	OM2K_MSGT_TF_CONF_REQ_REJ		= 0x00a3,
+	OM2K_MSGT_TF_CONF_RES_ACK		= 0x00a4,
+	OM2K_MSGT_TF_CONF_RES_NACK		= 0x00a5,
+	OM2K_MSGT_TF_CONF_RES			= 0x00a6,
+	OM2K_MSGT_TS_CONF_REQ			= 0x00a8,
+	OM2K_MSGT_TS_CONF_REQ_ACK		= 0x00aa,
+	OM2K_MSGT_TS_CONF_REQ_REJ		= 0x00ab,
+	OM2K_MSGT_TS_CONF_RES_ACK		= 0x00ac,
+	OM2K_MSGT_TS_CONF_RES_NACK		= 0x00ad,
+	OM2K_MSGT_TS_CONF_RES			= 0x00ae,
+	OM2K_MSGT_TX_CONF_REQ			= 0x00b0,
+	OM2K_MSGT_TX_CONF_REQ_ACK		= 0x00b2,
+	OM2K_MSGT_TX_CONF_REQ_REJ		= 0x00b3,
+	OM2K_MSGT_TX_CONF_RES_ACK		= 0x00b4,
+	OM2K_MSGT_TX_CONF_RES_NACK		= 0x00b5,
+	OM2K_MSGT_TX_CONF_RES			= 0x00b6,
+
+	OM2K_MSGT_NEGOT_REQ_ACK			= 0x0104,
+	OM2K_MSGT_NEGOT_REQ_NACK		= 0x0105,
+	OM2K_MSGT_NEGOT_REQ			= 0x0106,
+};
+
+enum abis_om2k_dei {
+	OM2K_DEI_BCC				= 0x06,
+	OM2K_DEI_BSIC				= 0x09,
+	OM2K_DEI_CAL_TIME			= 0x0d,
+	OM2K_DEI_COMBINATION			= 0x0f,
+	OM2K_DEI_CON_CONN_LIST			= 0x10,
+	OM2K_DEI_END_LIST_NR			= 0x13,
+	OM2K_DEI_FILLING_MARKER			= 0x1c,
+	OM2K_DEI_FN_OFFSET			= 0x1d,
+	OM2K_DEI_FREQ_LIST			= 0x1e,
+	OM2K_DEI_FREQ_SPEC_RX			= 0x1f,
+	OM2K_DEI_FREQ_SPEC_TX			= 0x20,
+	OM2K_DEI_HSN				= 0x21,
+	OM2K_DEI_IS_CONN_LIST			= 0x27,
+	OM2K_DEI_LIST_NR			= 0x28,
+	OM2K_DEI_MAIO				= 0x2b,
+	OM2K_DEI_OP_INFO			= 0x2e,
+	OM2K_DEI_POWER				= 0x2f,
+	OM2K_DEI_RX_DIVERSITY			= 0x33,
+	OM2K_DEI_TF_MODE			= 0x3a,
+	OM2K_DEI_TS_NR				= 0x3c,
+	OM2K_DEI_EXT_RANGE			= 0x47,
+	OM2K_DEI_NEGOT_REC1			= 0x90,
+	OM2K_DEI_NEGOT_REC2			= 0x91,
+	OM2K_DEI_FS_OFFSET			= 0x98,
+};
+
+static const struct value_string om2k_msgcode_vals[] = {
+	{ 0x0000, "Abort SP Command" },
+	{ 0x0002, "Abort SP Complete" },
+	{ 0x0004, "Alarm Report ACK" },
+	{ 0x0005, "Alarm Report NACK" },
+	{ 0x0006, "Alarm Report" },
+	{ 0x0008, "Alarm Status Request" },
+	{ 0x000a, "Alarm Status Request Accept" },
+	{ 0x000b, "Alarm Status Request Reject" },
+	{ 0x000c, "Alarm Status Result ACK" },
+	{ 0x000d, "Alarm Status Result NACK" },
+	{ 0x000e, "Alarm Status Result" },
+	{ 0x0010, "Calendar Time Response" },
+	{ 0x0011, "Calendar Time Reject" },
+	{ 0x0012, "Calendar Time Request" },
+	{ 0x0014, "CON Configuration Request" },
+	{ 0x0016, "CON Configuration Request Accept" },
+	{ 0x0017, "CON Configuration Request Reject" },
+	{ 0x0018, "CON Configuration Result ACK" },
+	{ 0x0019, "CON Configuration Result NACK" },
+	{ 0x001a, "CON Configuration Result" },
+	{ 0x001c, "Connect Command" },
+	{ 0x001e, "Connect Complete" },
+	{ 0x001f, "Connect Rejecte" },
+	{ 0x0028, "Disable Request" },
+	{ 0x002a, "Disable Request Accept" },
+	{ 0x002b, "Disable Request Reject" },
+	{ 0x002c, "Disable Result ACK" },
+	{ 0x002d, "Disable Result NACK" },
+	{ 0x002e, "Disable Result" },
+	{ 0x0030, "Disconnect Command" },
+	{ 0x0032, "Disconnect Complete" },
+	{ 0x0033, "Disconnect Reject" },
+	{ 0x0034, "Enable Request" },
+	{ 0x0036, "Enable Request Accept" },
+	{ 0x0037, "Enable Request Reject" },
+	{ 0x0038, "Enable Result ACK" },
+	{ 0x0039, "Enable Result NACK" },
+	{ 0x003a, "Enable Result" },
+	{ 0x003c, "Escape Downlink Normal" },
+	{ 0x003d, "Escape Downlink NACK" },
+	{ 0x003e, "Escape Uplink Normal" },
+	{ 0x003f, "Escape Uplink NACK" },
+	{ 0x0040, "Fault Report ACK" },
+	{ 0x0041, "Fault Report NACK" },
+	{ 0x0042, "Fault Report" },
+	{ 0x0044, "File Package End Command" },
+	{ 0x0046, "File Package End Result" },
+	{ 0x0047, "File Package End Reject" },
+	{ 0x0048, "File Relation Request" },
+	{ 0x004a, "File Relation Response" },
+	{ 0x004b, "File Relation Request Reject" },
+	{ 0x004c, "File Segment Transfer" },
+	{ 0x004e, "File Segment Transfer Complete" },
+	{ 0x004f, "File Segment Transfer Reject" },
+	{ 0x0050, "HW Information Request" },
+	{ 0x0052, "HW Information Request Accept" },
+	{ 0x0053, "HW Information Request Reject" },
+	{ 0x0054, "HW Information Result ACK" },
+	{ 0x0055, "HW Information Result NACK" },
+	{ 0x0056, "HW Information Result" },
+	{ 0x0060, "IS Configuration Request" },
+	{ 0x0062, "IS Configuration Request Accept" },
+	{ 0x0063, "IS Configuration Request Reject" },
+	{ 0x0064, "IS Configuration Result ACK" },
+	{ 0x0065, "IS Configuration Result NACK" },
+	{ 0x0066, "IS Configuration Result" },
+	{ 0x0068, "Load Data End" },
+	{ 0x006a, "Load Data End Result" },
+	{ 0x006b, "Load Data End Reject" },
+	{ 0x006c, "Load Data Init" },
+	{ 0x006e, "Load Data Init Accept" },
+	{ 0x006f, "Load Data Init Reject" },
+	{ 0x0070, "Loop Control Command" },
+	{ 0x0072, "Loop Control Complete" },
+	{ 0x0073, "Loop Control Reject" },
+	{ 0x0074, "Operational Information" },
+	{ 0x0076, "Operational Information Accept" },
+	{ 0x0077, "Operational Information Reject" },
+	{ 0x0078, "Reset Command" },
+	{ 0x007a, "Reset Complete" },
+	{ 0x007b, "Reset Reject" },
+	{ 0x007c, "RX Configuration Request" },
+	{ 0x007e, "RX Configuration Request Accept" },
+	{ 0x007f, "RX Configuration Request Reject" },
+	{ 0x0080, "RX Configuration Result ACK" },
+	{ 0x0081, "RX Configuration Result NACK" },
+	{ 0x0082, "RX Configuration Result" },
+	{ 0x0084, "Start Request" },
+	{ 0x0086, "Start Request Accept" },
+	{ 0x0087, "Start Request Reject" },
+	{ 0x0088, "Start Result ACK" },
+	{ 0x0089, "Start Result NACK" },
+	{ 0x008a, "Start Result" },
+	{ 0x008c, "Status Request" },
+	{ 0x008e, "Status Response" },
+	{ 0x008f, "Status Reject" },
+	{ 0x0094, "Test Request" },
+	{ 0x0096, "Test Request Accept" },
+	{ 0x0097, "Test Request Reject" },
+	{ 0x0098, "Test Result ACK" },
+	{ 0x0099, "Test Result NACK" },
+	{ 0x009a, "Test Result" },
+	{ 0x00a0, "TF Configuration Request" },
+	{ 0x00a2, "TF Configuration Request Accept" },
+	{ 0x00a3, "TF Configuration Request Reject" },
+	{ 0x00a4, "TF Configuration Result ACK" },
+	{ 0x00a5, "TF Configuration Result NACK" },
+	{ 0x00a6, "TF Configuration Result" },
+	{ 0x00a8, "TS Configuration Request" },
+	{ 0x00aa, "TS Configuration Request Accept" },
+	{ 0x00ab, "TS Configuration Request Reject" },
+	{ 0x00ac, "TS Configuration Result ACK" },
+	{ 0x00ad, "TS Configuration Result NACK" },
+	{ 0x00ae, "TS Configuration Result" },
+	{ 0x00b0, "TX Configuration Request" },
+	{ 0x00b2, "TX Configuration Request Accept" },
+	{ 0x00b3, "TX Configuration Request Reject" },
+	{ 0x00b4, "TX Configuration Result ACK" },
+	{ 0x00b5, "TX Configuration Result NACK" },
+	{ 0x00b6, "TX Configuration Result" },
+	{ 0x00bc, "DIP Alarm Report ACK" },
+	{ 0x00bd, "DIP Alarm Report NACK" },
+	{ 0x00be, "DIP Alarm Report" },
+	{ 0x00c0, "DIP Alarm Status Request" },
+	{ 0x00c2, "DIP Alarm Status Response" },
+	{ 0x00c3, "DIP Alarm Status Reject" },
+	{ 0x00c4, "DIP Quality Report I ACK" },
+	{ 0x00c5, "DIP Quality Report I NACK" },
+	{ 0x00c6, "DIP Quality Report I" },
+	{ 0x00c8, "DIP Quality Report II ACK" },
+	{ 0x00c9, "DIP Quality Report II NACK" },
+	{ 0x00ca, "DIP Quality Report II" },
+	{ 0x00dc, "DP Configuration Request" },
+	{ 0x00de, "DP Configuration Request Accept" },
+	{ 0x00df, "DP Configuration Request Reject" },
+	{ 0x00e0, "DP Configuration Result ACK" },
+	{ 0x00e1, "DP Configuration Result NACK" },
+	{ 0x00e2, "DP Configuration Result" },
+	{ 0x00e4, "Capabilities HW Info Report ACK" },
+	{ 0x00e5, "Capabilities HW Info Report NACK" },
+	{ 0x00e6, "Capabilities HW Info Report" },
+	{ 0x00e8, "Capabilities Request" },
+	{ 0x00ea, "Capabilities Request Accept" },
+	{ 0x00eb, "Capabilities Request Reject" },
+	{ 0x00ec, "Capabilities Result ACK" },
+	{ 0x00ed, "Capabilities Result NACK" },
+	{ 0x00ee, "Capabilities Result" },
+	{ 0x00f0, "FM Configuration Request" },
+	{ 0x00f2, "FM Configuration Request Accept" },
+	{ 0x00f3, "FM Configuration Request Reject" },
+	{ 0x00f4, "FM Configuration Result ACK" },
+	{ 0x00f5, "FM Configuration Result NACK" },
+	{ 0x00f6, "FM Configuration Result" },
+	{ 0x00f8, "FM Report Request" },
+	{ 0x00fa, "FM Report Response" },
+	{ 0x00fb, "FM Report Reject" },
+	{ 0x00fc, "FM Start Command" },
+	{ 0x00fe, "FM Start Complete" },
+	{ 0x00ff, "FM Start Reject" },
+	{ 0x0100, "FM Stop Command" },
+	{ 0x0102, "FM Stop Complete" },
+	{ 0x0103, "FM Stop Reject" },
+	{ 0x0104, "Negotiation Request ACK" },
+	{ 0x0105, "Negotiation Request NACK" },
+	{ 0x0106, "Negotiation Request" },
+	{ 0x0108, "BTS Initiated Request ACK" },
+	{ 0x0109, "BTS Initiated Request NACK" },
+	{ 0x010a, "BTS Initiated Request" },
+	{ 0x010c, "Radio Channels Release Command" },
+	{ 0x010e, "Radio Channels Release Complete" },
+	{ 0x010f, "Radio Channels Release Reject" },
+	{ 0x0118, "Feature Control Command" },
+	{ 0x011a, "Feature Control Complete" },
+	{ 0x011b, "Feature Control Reject" },
+
+	{ 0, NULL }
+};
+
+/* TS 12.21 Section 9.4: Attributes */
+static const struct value_string om2k_attr_vals[] = {
+	{ 0x00, "Accordance indication" },
+	{ 0x01, "Alarm Id" },
+	{ 0x02, "Alarm Data" },
+	{ 0x03, "Alarm Severity" },
+	{ 0x04, "Alarm Status" },
+	{ 0x05, "Alarm Status Type" },
+	{ 0x06, "BCC" },
+	{ 0x07, "BS_AG_BKS_RES" },
+	{ 0x09, "BSIC" },
+	{ 0x0a, "BA_PA_MFRMS" },
+	{ 0x0b, "CBCH Indicator" },
+	{ 0x0c, "CCCH Options" },
+	{ 0x0d, "Calendar Time" },
+	{ 0x0f, "Channel Combination" },
+	{ 0x10, "CON Connection List" },
+	{ 0x11, "Data End Indication" },
+	{ 0x12, "DRX_DEV_MAX" },
+	{ 0x13, "End List Number" },
+	{ 0x14, "External Condition Map Class 1" },
+	{ 0x15, "External Condition Map Class 2" },
+	{ 0x16, "File Relation Indication" },
+	{ 0x17, "File Revision" },
+	{ 0x18, "File Segment Data" },
+	{ 0x19, "File Segment Length" },
+	{ 0x1a, "File Segment Sequence Number" },
+	{ 0x1b, "File Size" },
+	{ 0x1c, "Filling Marker" },
+	{ 0x1d, "FN Offset" },
+	{ 0x1e, "Frequency List" },
+	{ 0x1f, "Frequency Specifier RX" },
+	{ 0x20, "Frequency Specifier TX" },
+	{ 0x21, "HSN" },
+	{ 0x22, "ICM Indicator" },
+	{ 0x23, "Internal Fault Map Class 1A" },
+	{ 0x24, "Internal Fault Map Class 1B" },
+	{ 0x25, "Internal Fault Map Class 2A" },
+	{ 0x26, "Internal Fault Map Class 2A Extension" },
+	{ 0x27, "IS Connection List" },
+	{ 0x28, "List Number" },
+	{ 0x29, "File Package State Indication" },
+	{ 0x2a, "Local Access State" },
+	{ 0x2b, "MAIO" },
+	{ 0x2c, "MO State" },
+	{ 0x2d, "Ny1" },
+	{ 0x2e, "Operational Information" },
+	{ 0x2f, "Power" },
+	{ 0x30, "RU Position Data" },
+	{ 0x31, "Protocol Error" },
+	{ 0x32, "Reason Code" },
+	{ 0x33, "Receiver Diversity" },
+	{ 0x34, "Replacement Unit Map" },
+	{ 0x35, "Result Code" },
+	{ 0x36, "RU Revision Data" },
+	{ 0x38, "T3105" },
+	{ 0x39, "Test Loop Setting" },
+	{ 0x3a, "TF Mode" },
+	{ 0x3b, "TF Compensation Value" },
+	{ 0x3c, "Time Slot Number" },
+	{ 0x3d, "TSC" },
+	{ 0x3e, "RU Logical Id" },
+	{ 0x3f, "RU Serial Number Data" },
+	{ 0x40, "BTS Version" },
+	{ 0x41, "OML IWD Version" },
+	{ 0x42, "RWL IWD Version" },
+	{ 0x43, "OML Function Map 1" },
+	{ 0x44, "OML Function Map 2" },
+	{ 0x45, "RSL Function Map 1" },
+	{ 0x46, "RSL Function Map 2" },
+	{ 0x47, "Extended Range Indicator" },
+	{ 0x48, "Request Indicators" },
+	{ 0x49, "DIP Alarm Condition Map" },
+	{ 0x4a, "ES Incoming" },
+	{ 0x4b, "ES Outgoing" },
+	{ 0x4e, "SES Incoming" },
+	{ 0x4f, "SES Outgoing" },
+	{ 0x50, "Replacement Unit Map Extension" },
+	{ 0x52, "UAS Incoming" },
+	{ 0x53, "UAS Outgoing" },
+	{ 0x58, "DF Incoming" },
+	{ 0x5a, "DF Outgoing" },
+	{ 0x5c, "SF" },
+	{ 0x60, "S Bits Setting" },
+	{ 0x61, "CRC-4 Use Option" },
+	{ 0x62, "T Parameter" },
+	{ 0x63, "N Parameter" },
+	{ 0x64, "N1 Parameter" },
+	{ 0x65, "N3 Parameter" },
+	{ 0x66, "N4 Parameter" },
+	{ 0x67, "P Parameter" },
+	{ 0x68, "Q Parameter" },
+	{ 0x69, "BI_Q1" },
+	{ 0x6a, "BI_Q2" },
+	{ 0x74, "ICM Boundary Parameters" },
+	{ 0x77, "AFT" },
+	{ 0x78, "AFT RAI" },
+	{ 0x79, "Link Supervision Control" },
+	{ 0x7a, "Link Supervision Filtering Time" },
+	{ 0x7b, "Call Supervision Time" },
+	{ 0x7c, "Interval Length UAS Incoming" },
+	{ 0x7d, "Interval Length UAS Outgoing" },
+	{ 0x7e, "ICM Channel Rate" },
+	{ 0x7f, "Attribute Identifier" },
+	{ 0x80, "FM Frequency List" },
+	{ 0x81, "FM Frequency Report" },
+	{ 0x82, "FM Percentile" },
+	{ 0x83, "FM Clear Indication" },
+	{ 0x84, "HW Info Signature" },
+	{ 0x85, "MO Record" },
+	{ 0x86, "TF Synchronisation Source" },
+	{ 0x87, "TTA" },
+	{ 0x88, "End Segment Number" },
+	{ 0x89, "Segment Number" },
+	{ 0x8a, "Capabilities Signature" },
+	{ 0x8c, "File Relation List" },
+	{ 0x90, "Negotiation Record I" },
+	{ 0x91, "Negotiation Record II" },
+	{ 0x92, "Encryption Algorithm" },
+	{ 0x94, "Interference Rejection Combining" },
+	{ 0x95, "Dedication Information" },
+	{ 0x97, "Feature Code" },
+	{ 0x98, "FS Offset" },
+	{ 0x99, "ESB Timeslot" },
+	{ 0x9a, "Master TG Instance" },
+	{ 0x9b, "Master TX Chain Delay" },
+	{ 0x9c, "External Condition Class 2 Extension" },
+	{ 0x9d, "TSs MO State" },
+	{ 0, NULL }
+};
+
+const struct value_string om2k_mo_class_short_vals[] = {
+	{ 0x01, "TRXC" },
+	{ 0x03, "TS" },
+	{ 0x04, "TF" },
+	{ 0x05, "IS" },
+	{ 0x06, "CON" },
+	{ 0x07, "DP" },
+	{ 0x0a, "CF" },
+	{ 0x0b, "TX" },
+	{ 0x0c, "RX" },
+	{ 0, NULL }
+};
+
+static struct msgb *om2k_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OM2000");
+}
+
+static char *om2k_mo_name(const struct abis_om2k_mo *mo)
+{
+	static char mo_buf[64];
+
+	memset(mo_buf, 0, sizeof(mo_buf));
+	snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x",
+		 get_value_string(om2k_mo_class_short_vals, mo->class),
+		 mo->bts, mo->assoc_so, mo->inst);
+	return mo_buf;
+}
+
+static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h;
+	int to_trx_oml;
+
+	msg->l2h = msg->data;
+	o2h = (struct abis_om2k_hdr *) msg->l2h;
+
+	switch (o2h->mo.class) {
+	case OM2K_MO_CLS_TRXC:
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_RX:
+	case OM2K_MO_CLS_TS:
+		/* Route through per-TRX OML Link to the appropriate TRX */
+		to_trx_oml = 1;
+		msg->trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst);
+		if (!msg->trx) {
+			LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
+				"non-existing TRX\n", om2k_mo_name(&o2h->mo));
+			return -ENODEV;
+		}
+		break;
+	default:
+		/* Route through the IXU/DXU OML Link */
+		msg->trx = bts->c0;
+		to_trx_oml = 0;
+		break;
+	}
+
+	return _abis_nm_sendmsg(msg, to_trx_oml);
+}
+
+static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo,
+			 uint16_t msg_type, uint8_t attr_len)
+{
+	o2h->om.mdisc = ABIS_OM_MDISC_FOM;
+	o2h->om.placement = ABIS_OM_PLACEMENT_ONLY;
+	o2h->om.sequence = 0;
+	o2h->om.length = 6 + attr_len;
+	o2h->msg_type = htons(msg_type);
+	memcpy(&o2h->mo, mo, sizeof(o2h->mo));
+}
+
+const struct abis_om2k_mo om2k_mo_cf = { OM2K_MO_CLS_CF, 0, 0xFF, 0 };
+const struct abis_om2k_mo om2k_mo_is = { OM2K_MO_CLS_IS, 0, 0xFF, 0 };
+const struct abis_om2k_mo om2k_mo_con = { OM2K_MO_CLS_CON, 0, 0xFF, 0 };
+const struct abis_om2k_mo om2k_mo_tf = { OM2K_MO_CLS_TF, 0, 0xFF, 0 };
+
+static int abis_om2k_cal_time_resp(struct gsm_bts *bts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	time_t tm_t;
+	struct tm *tm;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &om2k_mo_cf, OM2K_MSGT_CAL_TIME_RESP, 7);
+
+	tm_t = time(NULL);
+	tm = localtime(&tm_t);
+
+	msgb_put_u8(msg, OM2K_DEI_CAL_TIME);
+	msgb_put_u8(msg, tm->tm_year % 100);
+	msgb_put_u8(msg, tm->tm_mon + 1);
+	msgb_put_u8(msg, tm->tm_mday);
+	msgb_put_u8(msg, tm->tm_hour);
+	msgb_put_u8(msg, tm->tm_min);
+	msgb_put_u8(msg, tm->tm_sec);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+				uint8_t msg_type)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, mo, msg_type, 0);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+		get_value_string(om2k_msgcode_vals, msg_type));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD);
+}
+
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ);
+}
+
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ);
+}
+
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD);
+}
+
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD);
+}
+
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ);
+}
+
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ);
+}
+
+int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+	return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISABLE_REQ);
+}
+
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			 uint8_t operational)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO, 2);
+
+	msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts, struct om2k_is_conn_grp *cg,
+			     unsigned int num_cg )
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &om2k_mo_is, OM2K_MSGT_IS_CONF_REQ,
+		      2 + 2 + TLV_GROSS_LEN(num_cg * sizeof(*cg)));
+
+	msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1);
+	msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1);
+
+	msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST,
+		     num_cg * sizeof(*cg), (uint8_t *)cg);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_con_conf_req(struct gsm_bts *bts, uint8_t *data,
+			     unsigned int len)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &om2k_mo_con, OM2K_MSGT_CON_CONF_REQ,
+		      2 + 2 + TLV_GROSS_LEN(len));
+
+	msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1);
+	msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1);
+
+	msgb_tlv_put(msg, OM2K_DEI_CON_CONN_LIST, len, data);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+static void om2k_trx_to_mo(struct abis_om2k_mo *mo,
+			   const struct gsm_bts_trx *trx,
+			   enum abis_om2k_mo_cls cls)
+{
+	mo->class = cls;
+	mo->bts = 0;
+	mo->inst = trx->nr;
+	mo->assoc_so = 0;
+}
+
+static void om2k_ts_to_mo(struct abis_om2k_mo *mo,
+			  const struct gsm_bts_trx_ts *ts)
+{
+	mo->class = OM2K_MO_CLS_TS;
+	mo->bts = 0;
+	mo->inst = ts->nr;
+	mo->assoc_so = ts->trx->nr;
+}
+
+/* Configure a Receiver MO */
+int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct abis_om2k_mo mo;
+
+	om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX);
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ, 3+2);
+
+	msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn);
+	msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x03); /* A+B */
+
+	return abis_om2k_sendmsg(trx->bts, msg);
+}
+
+/* Configure a Transmitter MO */
+int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct abis_om2k_mo mo;
+
+	om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX);
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ, 3+2+2+2);
+
+	msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn);
+	msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red);
+	msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0);	/* Filling enabled */
+	msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7);
+	/* Dedication Information is optional */
+
+	return abis_om2k_sendmsg(trx->bts, msg);
+}
+
+enum abis_om2k_tf_mode {
+	OM2K_TF_MODE_MASTER	= 0x00,
+	OM2K_TF_MODE_STANDALONE	= 0x01,
+	OM2K_TF_MODE_SLAVE	= 0x02,
+	OM2K_TF_MODE_UNDEFINED	= 0xff,
+};
+
+static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff };
+
+int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &om2k_mo_tf, OM2K_MSGT_TF_CONF_REQ,
+			2+1+sizeof(fs_offset_undef));
+
+	msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE);
+	msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET,
+			  sizeof(fs_offset_undef), fs_offset_undef);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+static uint8_t pchan2comb(enum gsm_phys_chan_config pchan)
+{
+	switch (pchan) {
+	case GSM_PCHAN_CCCH:
+		return 4;
+	case GSM_PCHAN_CCCH_SDCCH4:
+		return 5;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+		return 3;
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+	case GSM_PCHAN_PDCH:
+	case GSM_PCHAN_TCH_F_PDCH:
+		return 8;
+	default:
+		return 0;
+	}
+}
+
+/* Compute a frequency list in OM2000 fomrmat */
+static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts)
+{
+	uint8_t *cur = list;
+
+	if (ts->hopping.enabled) {
+		unsigned int i;
+		for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+			if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) {
+				*cur++ = 0x00;
+				*cur++ = i >> 8;
+				*cur++ = i & 0xff;
+			}
+		}
+	} else {
+		*cur++ = 0x00; /* TX/RX address */
+		*cur++ = ts->trx->arfcn >> 8;
+		*cur++ = ts->trx->arfcn && 0xff;
+	}
+	return (cur - list);
+}
+
+int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+	struct abis_om2k_mo mo;
+	uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */
+	int freq_list_len;
+
+	om2k_ts_to_mo(&mo, ts);
+
+	freq_list_len = om2k_gen_freq_list(freq_list, ts);
+	if (freq_list_len < 0)
+		return freq_list_len;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ,
+			2+2+TLV_GROSS_LEN(freq_list_len)+2+2+2+2+3+2);
+
+	msgb_tv_put(msg, OM2K_DEI_COMBINATION, pchan2comb(ts->pchan));
+	msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr);
+	msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list);
+	msgb_tv_put(msg, OM2K_DEI_HSN, ts->hopping.hsn);
+	msgb_tv_put(msg, OM2K_DEI_MAIO, ts->hopping.maio);
+	msgb_tv_put(msg, OM2K_DEI_BSIC, ts->trx->bts->bsic);
+	msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x03); /* A+B */
+	msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0);
+	msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */
+	/* Optional: Interference Rejection Combining */
+
+	return abis_om2k_sendmsg(ts->trx->bts, msg);
+}
+
+static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+				      uint8_t *data, unsigned int len)
+{
+	struct msgb *msg = om2k_msgb_alloc();
+	struct abis_om2k_hdr *o2k;
+
+	o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+	fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK, 2+len);
+
+	msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data);
+
+	DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+		get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+struct iwd_version {
+	uint8_t gen_char[3+1];
+	uint8_t rev_char[3+1];
+};
+
+struct iwd_type {
+	uint8_t num_vers;
+	struct iwd_version v[8];
+};
+
+static int om2k_rx_negot_req(struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	struct iwd_type iwd_types[16];
+	uint8_t num_iwd_types = o2h->data[2];
+	uint8_t *cur = o2h->data+3;
+	unsigned int i, v;
+
+	uint8_t out_buf[1024];
+	uint8_t *out_cur = out_buf+1;
+	uint8_t out_num_types = 0;
+
+	memset(iwd_types, 0, sizeof(iwd_types));
+
+	/* Parse the RBS-supported IWD versions into iwd_types array */
+	for (i = 0; i < num_iwd_types; i++) {
+		uint8_t num_versions = *cur++;
+		uint8_t iwd_type = *cur++;
+
+		iwd_types[iwd_type].num_vers = num_versions;
+
+		for (v = 0; v < num_versions; v++) {
+			struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v];
+
+			memcpy(iwd_v->gen_char, cur, 3);
+			cur += 3;
+			memcpy(iwd_v->rev_char, cur, 3);
+			cur += 3;
+
+			DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type,
+				iwd_v->gen_char, iwd_v->rev_char);
+		}
+	}
+
+	/* Select the last version for each IWD type */
+	for (i = 0; i < ARRAY_SIZE(iwd_types); i++) {
+		struct iwd_type *type = &iwd_types[i];
+		struct iwd_version *last_v;
+
+		if (type->num_vers == 0)
+			continue;
+
+		out_num_types++;
+
+		last_v = &type->v[type->num_vers-1];
+
+		*out_cur++ = i;
+		memcpy(out_cur, last_v->gen_char, 3);
+		out_cur += 3;
+		memcpy(out_cur, last_v->rev_char, 3);
+		out_cur += 3;
+	}
+
+	out_buf[0] = out_num_types;
+
+	return abis_om2k_tx_negot_req_ack(msg->trx->bts, &o2h->mo, out_buf, out_cur - out_buf);
+}
+
+static int om2k_rx_start_res(struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	int rc;
+
+	rc = abis_om2k_tx_simple(msg->trx->bts, &o2h->mo, OM2K_MSGT_START_RES_ACK);
+	rc = abis_om2k_tx_op_info(msg->trx->bts, &o2h->mo, 1);
+
+	return rc;
+}
+
+static int om2k_rx_op_info_ack(struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+
+	/* FIXME: update Operational state in our structures */
+
+	return 0;
+}
+
+int abis_om2k_rcvmsg(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->trx->bts;
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	struct abis_om_hdr *oh = &o2h->om;
+	uint16_t msg_type = ntohs(o2h->msg_type);
+	int rc = 0;
+
+	/* Various consistency checks */
+	if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+			oh->placement);
+		if (oh->placement != ABIS_OM_PLACEMENT_FIRST)
+			return -EINVAL;
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+			oh->sequence);
+		return -EINVAL;
+	}
+
+	msg->l3h = (unsigned char *)o2h + sizeof(*o2h);
+
+	if (oh->mdisc != ABIS_OM_MDISC_FOM) {
+		LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n",
+			oh->mdisc);
+		return -EINVAL;
+	}
+
+	DEBUGP(DNM, "Rx MO=%s %s (%s)\n", om2k_mo_name(&o2h->mo),
+		get_value_string(om2k_msgcode_vals, msg_type),
+		hexdump(msg->l2h, msgb_l2len(msg)));
+
+	switch (msg_type) {
+	case OM2K_MSGT_CAL_TIME_REQ:
+		rc = abis_om2k_cal_time_resp(bts);
+		break;
+	case OM2K_MSGT_FAULT_REP:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_FAULT_REP_ACK);
+		break;
+	case OM2K_MSGT_NEGOT_REQ:
+		rc = om2k_rx_negot_req(msg);
+		break;
+	case OM2K_MSGT_START_RES:
+		rc = om2k_rx_start_res(msg);
+		break;
+	case OM2K_MSGT_OP_INFO_ACK:
+		rc = om2k_rx_op_info_ack(msg);
+		break;
+	case OM2K_MSGT_IS_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_CON_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_TX_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_RX_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_TS_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_TF_CONF_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK);
+		break;
+	case OM2K_MSGT_CONNECT_COMPL:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RESET_CMD);
+		break;
+	case OM2K_MSGT_RESET_COMPL:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_REQ);
+		break;
+	case OM2K_MSGT_ENABLE_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK);
+		break;
+	case OM2K_MSGT_DISABLE_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK);
+		break;
+	case OM2K_MSGT_TEST_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK);
+		break;
+	case OM2K_MSGT_STATUS_RESP:
+		break;
+	case OM2K_MSGT_START_REQ_ACK:
+	case OM2K_MSGT_CON_CONF_REQ_ACK:
+	case OM2K_MSGT_IS_CONF_REQ_ACK:
+	case OM2K_MSGT_TX_CONF_REQ_ACK:
+	case OM2K_MSGT_RX_CONF_REQ_ACK:
+	case OM2K_MSGT_TS_CONF_REQ_ACK:
+	case OM2K_MSGT_TF_CONF_REQ_ACK:
+	case OM2K_MSGT_ENABLE_REQ_ACK:
+	case OM2K_MSGT_ALARM_STATUS_REQ_ACK:
+	case OM2K_MSGT_DISABLE_REQ_ACK:
+		break;
+	default:
+		LOGP(DNM, LOGL_NOTICE, "Rx unhandled OM2000 msg %s\n",
+			get_value_string(om2k_msgcode_vals, msg_type));
+	}
+
+	msgb_free(msg);
+	return rc;
+}
diff --git a/src/libbsc/abis_om2000_vty.c b/src/libbsc/abis_om2000_vty.c
new file mode 100644
index 0000000..5ebb2a3
--- /dev/null
+++ b/src/libbsc/abis_om2000_vty.c
@@ -0,0 +1,513 @@
+/* VTY interface for A-bis OM2000 */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct cmd_node om2k_node = {
+	OM2K_NODE,
+	"%s(om2k)# ",
+	1,
+};
+
+struct oml_node_state {
+	struct gsm_bts *bts;
+	struct abis_om2k_mo mo;
+};
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+/* FIXME: auto-generate those strings from the value_string lists */
+#define OM2K_OBJCLASS_VTY "(trxc|ts|tf|is|con|dp|cf|tx|rx)"
+#define OM2K_OBJCLASS_VTY_HELP 	"TRX Controller\n"	\
+				"Timeslot\n"		\
+				"Timing Function\n"	\
+				"Interface Switch\n"	\
+				"Abis Concentrator\n"	\
+				"Digital Path\n"	\
+				"Central Function\n"	\
+				"Transmitter\n"		\
+				"Receiver\n"
+
+DEFUN(om2k_class_inst, om2k_class_inst_cmd,
+	"bts <0-255> om2000 class " OM2K_OBJCLASS_VTY
+					" <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OM2000 managed objects\n"
+	"Object Class\n" 	OM2K_OBJCLASS_VTY_HELP
+	"BTS Number\n" "Associated SO Instance\n" "Instance Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(bsc_gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (bts->type != GSM_BTS_TYPE_RBS2000) {
+		vty_out(vty, "%% BTS %d not an Ericsson RBS%s",
+			bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]);
+	oms->mo.bts = atoi(argv[2]);
+	oms->mo.assoc_so = atoi(argv[3]);
+	oms->mo.inst = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OM2K_NODE;
+
+	return CMD_SUCCESS;
+
+}
+
+DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd,
+	"bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>",
+	"BTS related commands\n" "BTS Number\n"
+	"Manipulate the OML managed objects\n"
+	"Object Class\n" "Object Class\n"
+	"BTS Number\n" "Associated SO Instance\n" "Instance Number\n")
+{
+	struct gsm_bts *bts;
+	struct oml_node_state *oms;
+	int bts_nr = atoi(argv[0]);
+
+	bts = gsm_bts_num(bsc_gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+	if (!oms)
+		return CMD_WARNING;
+
+	oms->bts = bts;
+	oms->mo.class = atoi(argv[1]);
+	oms->mo.bts = atoi(argv[2]);
+	oms->mo.assoc_so = atoi(argv[3]);
+	oms->mo.inst = atoi(argv[4]);
+
+	vty->index = oms;
+	vty->node = OM2K_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_reset, om2k_reset_cmd,
+	"reset-command",
+	"Reset the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_reset_cmd(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_start, om2k_start_cmd,
+	"start-request",
+	"Start the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_start_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_status, om2k_status_cmd,
+	"status-request",
+	"Get the MO Status\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_status_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_connect, om2k_connect_cmd,
+	"connect-command",
+	"Connect the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_connect_cmd(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_disconnect, om2k_disconnect_cmd,
+	"disconnect-command",
+	"Disconnect the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_enable, om2k_enable_cmd,
+	"enable-request",
+	"Enable the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_enable_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_disable, om2k_disable_cmd,
+	"disable-request",
+	"Disable the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_disable_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_op_info, om2k_op_info_cmd,
+	"operational-info <0-1>",
+	"Set operational information\n")
+{
+	struct oml_node_state *oms = vty->index;
+	int oper = atoi(argv[0]);
+
+	abis_om2k_tx_op_info(oms->bts, &oms->mo, oper);
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_test, om2k_test_cmd,
+	"test-request",
+	"Test the MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	abis_om2k_tx_test_req(oms->bts, &oms->mo);
+	return CMD_SUCCESS;
+}
+
+struct con_conn_group {
+	struct llist_head list;
+
+	uint8_t cg;
+	uint16_t ccp;
+	uint8_t tag;
+	uint8_t tei;
+};
+
+static void add_con_list(struct gsm_bts *bts, uint8_t cg, uint16_t ccp,
+			 uint8_t tag, uint8_t tei)
+{
+	struct con_conn_group *ent = talloc_zero(bts, struct con_conn_group);
+
+	ent->cg = cg;
+	ent->ccp = ccp;
+	ent->tag = tag;
+	ent->tei = tei;
+
+	llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups);
+}
+
+static int del_con_list(struct gsm_bts *bts, uint8_t cg, uint16_t ccp,
+			uint8_t tag, uint8_t tei)
+{
+	struct con_conn_group *grp, *grp2;
+
+	llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.con.conn_groups, list) {
+		if (grp->cg == cg && grp->ccp == ccp && grp->tag == tag
+		    && grp->tei == tei) {
+			llist_del(&grp->list);
+			talloc_free(grp);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+#define CON_LIST_HELP	"CON connetiton list\n"			\
+			"Add entry to CON list\n"		\
+			"Delete entry from CON list\n"		\
+			"Connection Group Number\n"		\
+			"CON Connection Point\n"		\
+
+DEFUN(om2k_con_list_dec, om2k_con_list_dec_cmd,
+	"con-connection-list (add|del) <1-255> <0-1023> deconcentrated",
+	CON_LIST_HELP "De-concentrated in/outlet\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	uint8_t cg = atoi(argv[1]);
+	uint16_t ccp = atoi(argv[2]);
+
+	if (!strcmp(argv[0], "add"))
+		add_con_list(bts, cg, ccp, 0, 0xff);
+	else {
+		if (del_con_list(bts, cg, ccp, 0, 0xff) < 0) {
+			vty_out(vty, "%% No matching CON list entry%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_con_list_tei, om2k_con_list_tei_cmd,
+	"con-connection-list (add|del) <1-255> <0-1023> tei <0-63>",
+	CON_LIST_HELP "Concentrated in/outlet with TEI\n" "TEI Number\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	uint8_t cg = atoi(argv[1]);
+	uint16_t ccp = atoi(argv[2]);
+	uint8_t tei = atoi(argv[3]);
+
+	if (!strcmp(argv[0], "add"))
+		add_con_list(bts, cg, ccp, cg, tei);
+	else {
+		if (del_con_list(bts, cg, ccp, cg, tei) < 0) {
+			vty_out(vty, "%% No matching CON list entry%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1,
+				  uint16_t icp2, uint8_t cont_idx)
+{
+	grp->icp1 = htons(icp1);
+	grp->icp2 = htons(icp2);
+	grp->cont_idx = cont_idx;
+}
+
+struct is_conn_group {
+	struct llist_head list;
+	uint16_t icp1;
+	uint16_t icp2;
+	uint8_t ci;
+};
+
+DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd,
+	"is-connection-list (add|del) <0-2047> <0-2047> <0-255>",
+	"Interface Switch Connnection List\n"
+	"Add to IS list\n" "Delete from IS list\n"
+	"ICP1\n" "ICP2\n" "Contiguity Index\n")
+{
+	struct gsm_bts *bts = vty->index;
+	uint16_t icp1 = atoi(argv[1]);
+	uint16_t icp2 = atoi(argv[2]);
+	uint8_t ci = atoi(argv[3]);
+	struct is_conn_group *grp, *grp2;
+
+	if (!strcmp(argv[0], "add")) {
+		grp = talloc_zero(bts, struct is_conn_group);
+		grp->icp1 = icp1;
+		grp->icp2 = icp2;
+		grp->ci = ci;
+		llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups);
+	} else {
+		llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) {
+			if (grp->icp1 == icp1 && grp->icp2 == icp2
+			    && grp->ci == ci) {
+				llist_del(&grp->list);
+				talloc_free(grp);
+				return CMD_SUCCESS;
+			}
+		}
+		vty_out(vty, "%% No matching IS Conn Group found!%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(om2k_is_conf_req, om2k_is_conf_req_cmd,
+	"is-conf-req",
+	"Send IS Configuration Request\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	struct is_conn_group *grp;
+	unsigned int num_grps = 0, i = 0;
+	struct om2k_is_conn_grp *o2grps;
+
+	/* count number of groups in linked list */
+	llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+		num_grps++;
+
+	if (!num_grps) {
+		vty_out(vty, "%% No IS connection groups configured!%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* allocate buffer for oml group array */
+	o2grps = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps);
+
+	/* fill array with data from linked list */
+	llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+		om2k_fill_is_conn_grp(&o2grps[i++], grp->icp1, grp->icp2, grp->ci);
+
+	/* send the actual OML request */
+	abis_om2k_tx_is_conf_req(oms->bts, o2grps, num_grps);
+
+	talloc_free(o2grps);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_conf_req, om2k_conf_req_cmd,
+	"configuration-request",
+	"Send the configuration request for current MO\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	struct gsm_bts_trx *trx = NULL;
+	struct gsm_bts_trx_ts *ts = NULL;
+
+	switch (oms->mo.class) {
+	case OM2K_MO_CLS_TS:
+		trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so);
+		if (!trx) {
+			vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
+				oms->mo.assoc_so, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) {
+			vty_out(vty, "%% Timeslot %u out of range%s",
+				oms->mo.inst, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &trx->ts[oms->mo.inst];
+		abis_om2k_tx_ts_conf_req(ts);
+		break;
+	case OM2K_MO_CLS_RX:
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_TRXC:
+		trx = gsm_bts_trx_by_nr(bts, oms->mo.inst);
+		if (!trx) {
+			vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
+				oms->mo.inst, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		switch (oms->mo.class) {
+		case OM2K_MO_CLS_RX:
+			abis_om2k_tx_rx_conf_req(trx);
+			break;
+		case OM2K_MO_CLS_TX:
+			abis_om2k_tx_rx_conf_req(trx);
+			break;
+		default:
+			break;
+		}
+		break;
+	case OM2K_MO_CLS_TF:
+		abis_om2k_tx_tf_conf_req(bts);
+		break;
+	default:
+		vty_out(vty, "%% Don't know how to configure MO%s",
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	struct is_conn_group *igrp;
+	struct con_conn_group *cgrp;
+
+	llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list)
+		vty_out(vty, "  is-connection-list add %u %u %u%s",
+			igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE);
+
+	llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) {
+		vty_out(vty, "  con-connection-list add %u %u ",
+			cgrp->cg, cgrp->ccp);
+		if (cgrp->tei == 0xff)
+			vty_out(vty, "deconcentrated%s", VTY_NEWLINE);
+		else
+			vty_out(vty, "tei %u%s", cgrp->tei, VTY_NEWLINE);
+	}
+}
+
+int abis_om2k_vty_init(void)
+{
+	install_element(ENABLE_NODE, &om2k_class_inst_cmd);
+	install_element(ENABLE_NODE, &om2k_classnum_inst_cmd);
+	install_node(&om2k_node, dummy_config_write);
+
+	install_default(OM2K_NODE);
+	install_element(OM2K_NODE, &ournode_exit_cmd);
+	install_element(OM2K_NODE, &om2k_reset_cmd);
+	install_element(OM2K_NODE, &om2k_start_cmd);
+	install_element(OM2K_NODE, &om2k_status_cmd);
+	install_element(OM2K_NODE, &om2k_connect_cmd);
+	install_element(OM2K_NODE, &om2k_disconnect_cmd);
+	install_element(OM2K_NODE, &om2k_enable_cmd);
+	install_element(OM2K_NODE, &om2k_disable_cmd);
+	install_element(OM2K_NODE, &om2k_op_info_cmd);
+	install_element(OM2K_NODE, &om2k_test_cmd);
+	install_element(OM2K_NODE, &om2k_conf_req_cmd);
+	install_element(OM2K_NODE, &om2k_is_conf_req_cmd);
+	install_element(OM2K_NODE, &om2k_con_list_dec_cmd);
+	install_element(OM2K_NODE, &om2k_con_list_tei_cmd);
+
+	install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd);
+
+	return 0;
+}
diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c
new file mode 100644
index 0000000..9a4dc5b
--- /dev/null
+++ b/src/libbsc/abis_rsl.c
@@ -0,0 +1,1971 @@
+/* GSM Radio Signalling Link messages on the A-bis interface
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/debug.h>
+#include <osmocore/tlv.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/rtp_proxy.h>
+#include <osmocore/rsl.h>
+
+#include <osmocore/talloc.h>
+
+#define RSL_ALLOC_SIZE		1024
+#define RSL_ALLOC_HEADROOM	128
+
+#define MAX(a, b) (a) >= (b) ? (a) : (b)
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan);
+
+static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan,
+			      struct gsm_meas_rep *resp)
+{
+	struct lchan_signal_data sig;
+	sig.lchan = lchan;
+	sig.mr = resp;
+	dispatch_signal(SS_LCHAN, sig_no, &sig);
+}
+
+static u_int8_t mdisc_by_msgtype(u_int8_t msg_type)
+{
+	/* mask off the transparent bit ? */
+	msg_type &= 0xfe;
+
+	if ((msg_type & 0xf0) == 0x00)
+		return ABIS_RSL_MDISC_RLL;
+	if ((msg_type & 0xf0) == 0x10) {
+		if (msg_type >= 0x19 && msg_type <= 0x22)
+			return ABIS_RSL_MDISC_TRX;
+		else
+			return ABIS_RSL_MDISC_COM_CHAN;
+	}
+	if ((msg_type & 0xe0) == 0x20)
+		return ABIS_RSL_MDISC_DED_CHAN;
+	
+	return ABIS_RSL_MDISC_LOC;
+}
+
+static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh,
+				  u_int8_t msg_type)
+{
+	dh->c.msg_discr = mdisc_by_msgtype(msg_type);
+	dh->c.msg_type = msg_type;
+	dh->ie_chan = RSL_IE_CHAN_NR;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, u_int8_t chan_nr)
+{
+	struct gsm_lchan *lchan;
+	u_int8_t ts_nr = chan_nr & 0x07;
+	u_int8_t cbits = chan_nr >> 3;
+	u_int8_t lch_idx;
+	struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+
+	if (cbits == 0x01) {
+		lch_idx = 0;	/* TCH/F */	
+		if (ts->pchan != GSM_PCHAN_TCH_F &&
+		    ts->pchan != GSM_PCHAN_PDCH &&
+		    ts->pchan != GSM_PCHAN_TCH_F_PDCH)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x1e) == 0x02) {
+		lch_idx = cbits & 0x1;	/* TCH/H */
+		if (ts->pchan != GSM_PCHAN_TCH_H)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x1c) == 0x04) {
+		lch_idx = cbits & 0x3;	/* SDCCH/4 */
+		if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x18) == 0x08) {
+		lch_idx = cbits & 0x7;	/* SDCCH/8 */
+		if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+		lch_idx = 0;
+		if (ts->pchan != GSM_PCHAN_CCCH &&
+		    ts->pchan != GSM_PCHAN_CCCH_SDCCH4)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+		/* FIXME: we should not return first sdcch4 !!! */
+	} else {
+		LOGP(DRSL, LOGL_ERROR, "unknown chan_nr=0x%02x\n", chan_nr);
+		return NULL;
+	}
+
+	lchan = &ts->lchan[lch_idx];
+	log_set_context(BSC_CTX_LCHAN, lchan);
+	if (lchan->conn)
+		log_set_context(BSC_CTX_SUBSCR, lchan->conn->subscr);
+
+	return lchan;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+static u_int8_t ts2chan_nr(const struct gsm_bts_trx_ts *ts, uint8_t lchan_nr)
+{
+	u_int8_t cbits, chan_nr;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_PDCH:
+	case GSM_PCHAN_TCH_F_PDCH:
+		cbits = 0x01;
+		break;
+	case GSM_PCHAN_TCH_H:
+		cbits = 0x02;
+		cbits += lchan_nr;
+		break;
+	case GSM_PCHAN_CCCH_SDCCH4:
+		cbits = 0x04;
+		cbits += lchan_nr;
+		break;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+		cbits = 0x08;
+		cbits += lchan_nr;
+		break;
+	default:
+	case GSM_PCHAN_CCCH:
+		cbits = 0x10;
+		break;
+	}
+
+	chan_nr = (cbits << 3) | (ts->nr & 0x7);
+
+	return chan_nr;
+}
+
+u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+	return ts2chan_nr(lchan->ts, lchan->nr);
+}
+
+/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */
+u_int64_t str_to_imsi(const char *imsi_str)
+{
+	u_int64_t ret;
+
+	ret = strtoull(imsi_str, NULL, 10);
+
+	return ret;
+}
+
+/* Table 5 Clause 7 TS 05.02 */
+unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res)
+{
+	if (!bs_ccch_sdcch_comb)
+		return 9 - bs_ag_blks_res;
+	else
+		return 3 - bs_ag_blks_res;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+unsigned int get_ccch_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			    unsigned int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) / n_pag_blocks;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			      int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) % n_pag_blocks;
+}
+
+static struct msgb *rsl_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM,
+				   "RSL");
+}
+
+#define MACBLOCK_SIZE	23
+static void pad_macblock(u_int8_t *out, const u_int8_t *in, int len)
+{
+	memcpy(out, in, len);
+
+	if (len < MACBLOCK_SIZE)
+		memset(out+len, 0x2b, MACBLOCK_SIZE-len);
+}
+
+/* Chapter 9.3.7: Encryption Information */
+static int build_encr_info(u_int8_t *out, struct gsm_lchan *lchan)
+{
+	*out++ = lchan->encr.alg_id & 0xff;
+	if (lchan->encr.key_len)
+		memcpy(out, lchan->encr.key, lchan->encr.key_len);
+	return lchan->encr.key_len + 1;
+}
+
+static void print_rsl_cause(int lvl, const u_int8_t *cause_v, u_int8_t cause_len)
+{
+	int i;
+
+	LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ",
+		cause_v[0], rsl_err_name(cause_v[0]));
+	for (i = 1; i < cause_len-1; i++)
+		LOGPC(DRSL, lvl, "%02x ", cause_v[i]);
+}
+
+/* Send a BCCH_INFO message as per Chapter 8.5.1 */
+int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type,
+		  const u_int8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh);
+	init_dchan_hdr(dh, RSL_MT_BCCH_INFO);
+	dh->chan_nr = RSL_CHAN_BCCH;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type,
+		      const u_int8_t *data, int len)
+{
+	struct abis_rsl_common_hdr *ch;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+	ch->msg_discr = ABIS_RSL_MDISC_TRX;
+	ch->msg_type = RSL_MT_SACCH_FILL;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_info_modify(struct gsm_lchan *lchan, u_int8_t type,
+			  const u_int8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+
+	db = abs(db);
+	if (db > 30)
+		return -EINVAL;
+
+	msg = rsl_msgb_alloc();
+
+	lchan->bs_power = db/2;
+	if (fpc)
+		lchan->bs_power |= 0x10;
+	
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	int ctl_lvl;
+
+	ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm);
+	if (ctl_lvl < 0)
+		return ctl_lvl;
+
+	msg = rsl_msgb_alloc();
+
+	lchan->ms_power = ctl_lvl;
+
+	if (fpc)
+		lchan->ms_power |= 0x20;
+	
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
+				   struct gsm_lchan *lchan)
+{
+	memset(cm, 0, sizeof(cm));
+
+	/* FIXME: what to do with data calls ? */
+	if (lchan->ts->trx->bts->network->dtx_enabled)
+		cm->dtx_dtu = 0x03;
+	else
+		cm->dtx_dtu = 0x00;
+
+	/* set TCH Speech/Data */
+	cm->spd_ind = lchan->rsl_cmode;
+
+	if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN &&
+	    lchan->tch_mode != GSM48_CMODE_SIGN)
+		LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, "
+			"but tch_mode != signalling\n");
+
+	switch (lchan->type) {
+	case GSM_LCHAN_SDCCH:
+		cm->chan_rt = RSL_CMOD_CRT_SDCCH;
+		break;
+	case GSM_LCHAN_TCH_F:
+		cm->chan_rt = RSL_CMOD_CRT_TCH_Bm;
+		break;
+	case GSM_LCHAN_TCH_H:
+		cm->chan_rt = RSL_CMOD_CRT_TCH_Lm;
+		break;
+	case GSM_LCHAN_NONE:
+	case GSM_LCHAN_UNKNOWN:
+	default:
+		return -EINVAL;
+	}
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SIGN:
+		cm->chan_rate = 0;
+		break;
+	case GSM48_CMODE_SPEECH_V1:
+		cm->chan_rate = RSL_CMOD_SP_GSM1;
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		cm->chan_rate = RSL_CMOD_SP_GSM2;
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		cm->chan_rate = RSL_CMOD_SP_GSM3;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+		cm->chan_rate = RSL_CMOD_SP_NT_14k5;
+		break;
+	case GSM48_CMODE_DATA_12k0:
+		cm->chan_rate = RSL_CMOD_SP_NT_12k0;
+		break;
+	case GSM48_CMODE_DATA_6k0:
+		cm->chan_rate = RSL_CMOD_SP_NT_6k0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Chapter 8.4.1 */
+#if 0
+int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr,
+		      u_int8_t act_type,
+		      struct rsl_ie_chan_mode *chan_mode,
+		      struct rsl_ie_chan_ident *chan_ident,
+		      u_int8_t bs_power, u_int8_t ms_power,
+		      u_int8_t ta)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	/* For compatibility with Phase 1 */
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(*chan_mode),
+		     (u_int8_t *) chan_mode);
+	msgb_tlv_put(msg, RSL_IE_CHAN_IDENT, 4,
+		     (u_int8_t *) chan_ident);
+#if 0
+	msgb_tlv_put(msg, RSL_IE_ENCR_INFO, 1,
+		     (u_int8_t *) &encr_info);
+#endif
+	msgb_tv_put(msg, RSL_IE_BS_POWER, bs_power);
+	msgb_tv_put(msg, RSL_IE_MS_POWER, ms_power);
+	msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+#endif
+
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type,
+			    u_int8_t ta, u_int8_t ho_ref)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+	uint8_t *len;
+
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	struct rsl_ie_chan_mode cm;
+	struct gsm48_chan_desc cd;
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0)
+		return rc;
+
+	memset(&cd, 0, sizeof(cd));
+	gsm48_lchan2chan_desc(&cd, lchan);
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (u_int8_t *) &cm);
+
+	/*
+	 * The Channel Identification is needed for Phase1 phones
+	 * and it contains the GSM48 Channel Description and the
+	 * Mobile Allocation. The GSM 08.58 asks for the Mobile
+	 * Allocation to have a length of zero. We are using the
+	 * msgb_l3len to calculate the length of both messages.
+	 */
+	msgb_v_put(msg, RSL_IE_CHAN_IDENT);
+	len = msgb_put(msg, 1);
+	msgb_tlv_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd);
+
+	if (lchan->ts->hopping.enabled)
+		msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len,
+			     lchan->ts->hopping.ma_data);
+	else
+		msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL);
+
+	/* update the calculated size */
+	msg->l3h = len + 1;
+	*len = msgb_l3len(msg);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	switch (act_type) {
+	case RSL_ACT_INTER_ASYNC:
+	case RSL_ACT_INTER_SYNC:
+		msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref);
+		break;
+	default:
+		break;
+	}
+
+	msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+	msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+	msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf),
+			     (u_int8_t *) &lchan->mr_conf);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.9: Modify channel mode on BTS side */
+int rsl_chan_mode_modify_req(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	struct rsl_ie_chan_mode cm;
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0)
+		return rc;
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ);
+	dh->chan_nr = chan_nr;
+
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (u_int8_t *) &cm);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf),
+			     (u_int8_t *) &lchan->mr_conf);
+	}
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.6: Send the encryption command with given L3 info */
+int rsl_encryption_cmd(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct gsm_lchan *lchan = msg->lchan;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+	u_int8_t l3_len = msg->len;
+	int rc;
+
+	/* First push the L3 IE tag and length */
+	msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
+
+	/* then the link identifier (SAPI0, main sign link) */
+	msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0);
+
+	/* then encryption information */
+	rc = build_encr_info(encr_info, lchan);
+	if (rc <= 0)
+		return rc;
+	msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+
+	/* and finally the DCHAN header */
+	dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_ENCR_CMD);
+	dh->chan_nr = chan_nr;
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */
+int rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH);
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->trx = lchan->ts->trx;
+
+	DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan));
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static void error_timeout_cb(void *data)
+{
+	struct gsm_lchan *lchan = data;
+	if (lchan->state != LCHAN_S_REL_ERR) {
+		LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n",
+		     gsm_lchan_name(lchan), lchan->state);
+		return;
+	}
+
+	/* go back to the none state */
+	LOGP(DRSL, LOGL_NOTICE, "%s is back in operation.\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan);
+
+/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */
+static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	if (lchan->state == LCHAN_S_REL_ERR) {
+		LOGP(DRSL, LOGL_NOTICE, "%s is in error state not sending release.\n",
+		     gsm_lchan_name(lchan));
+		return -1;
+	}
+
+	msg = rsl_msgb_alloc();
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL);
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->trx = lchan->ts->trx;
+
+	DEBUGP(DRSL, "%s RF Channel Release CMD due error %d\n", gsm_lchan_name(lchan), error);
+
+	if (error) {
+		/*
+		 * the nanoBTS sends RLL release indications after the channel release. This can
+		 * be a problem when we have reassigned the channel to someone else and then can
+		 * not figure out who used this channel.
+		 */
+		rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR);
+		lchan->error_timer.data = lchan;
+		lchan->error_timer.cb = error_timeout_cb;
+		bsc_schedule_timer(&lchan->error_timer,
+				   msg->trx->bts->network->T3111 + 2, 0);
+	}
+
+	rc =  abis_rsl_sendmsg(msg);
+
+	/* BTS will respond by RF CHAN REL ACK */
+#ifdef HSL_SR_1_0
+	/* The HSL Femto seems to 'forget' sending a REL ACK for TS1...TS7 */
+	if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO && lchan->ts->nr != 0)
+		rc = rsl_rx_rf_chan_rel_ack(lchan);
+#endif
+
+	return rc;
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan)
+{
+
+	DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan));
+
+	if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR)
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n",
+			gsm_lchan_name(lchan),
+			gsm_lchans_name(lchan->state));
+	bsc_del_timer(&lchan->T3111);
+	/* we have an error timer pending to release that */
+	if (lchan->state != LCHAN_S_REL_ERR)
+		rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+	lchan_free(lchan);
+
+	return 0;
+}
+
+int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len,
+		   u_int8_t *ms_ident, u_int8_t chan_needed)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_PAGING_CMD);
+	dh->chan_nr = RSL_CHAN_PCH_AGCH;
+
+	msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group);
+	msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2);
+	msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed);
+
+	msg->trx = bts->c0;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int imsi_str2bcd(u_int8_t *bcd_out, const char *str_in)
+{
+	int i, len = strlen(str_in);
+
+	for (i = 0; i < len; i++) {
+		int num = str_in[i] - 0x30;
+		if (num < 0 || num > 9)
+			return -1;
+		if (i % 2 == 0)
+			bcd_out[i/2] = num;
+		else
+			bcd_out[i/2] |= (num << 4);
+	}
+
+	return 0;
+}
+
+/* Chapter 8.5.6 */
+int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int8_t buf[MACBLOCK_SIZE];
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD);
+	dh->chan_nr = RSL_CHAN_PCH_AGCH;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val);
+		break;
+	default:
+		/* If phase 2, construct a FULL_IMM_ASS_INFO */
+		pad_macblock(buf, val, len);
+		msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, MACBLOCK_SIZE, buf);
+		break;
+	}
+
+	msg->trx = bts->c0;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Send Siemens specific MS RF Power Capability Indication */
+int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI);
+	dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	dh->chan_nr = lchan2chan_nr(lchan);
+	msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(u_int8_t *)mrpci);
+
+	DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n",
+		gsm_lchan_name(lchan), *(u_int8_t *)mrpci);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+
+/* Send "DATA REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_data_request(struct msgb *msg, u_int8_t link_id)
+{
+	if (msg->lchan == NULL) {
+		LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n");
+		return -EINVAL;
+	}
+
+	rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, lchan2chan_nr(msg->lchan),
+			link_id, 1);
+
+	msg->trx = msg->lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Send "ESTABLISH REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id)
+{
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_EST_REQ, lchan2chan_nr(lchan),
+			     link_id, 0);
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection.
+   This is what higher layers should call.  The BTS then responds with
+   RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE,
+   which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls
+   lchan_free() */
+int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t reason)
+{
+
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_REL_REQ, lchan2chan_nr(lchan),
+			     link_id, 0);
+	/* 0 is normal release, 1 is local end */
+	msgb_tv_put(msg, RSL_IE_RELEASE_MODE, reason);
+
+	/* FIXME: start some timer in case we don't receive a REL ACK ? */
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int state)
+{
+	lchan->state = state;
+	return 0;
+}
+
+/* Chapter 8.4.2: Channel Activate Acknowledge */
+static int rsl_rx_chan_act_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+
+	/* BTS has confirmed channel activation, we now need
+	 * to assign the activated channel to the MS */
+	if (rslh->ie_chan != RSL_IE_CHAN_NR)
+		return -EINVAL;
+
+	if (msg->lchan->state != LCHAN_S_ACT_REQ)
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n",
+			gsm_lchan_name(msg->lchan),
+			gsm_lchans_name(msg->lchan->state));
+	rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE);
+
+	if (msg->lchan->rqd_ref) {
+		rsl_send_imm_assignment(msg->lchan);
+		talloc_free(msg->lchan->rqd_ref);
+		msg->lchan->rqd_ref = NULL;
+		msg->lchan->rqd_ta = 0;
+	}
+
+	send_lchan_signal(S_LCHAN_ACTIVATE_ACK, msg->lchan, NULL);
+
+	return 0;
+}
+
+/* Chapter 8.4.3: Channel Activate NACK */
+static int rsl_rx_chan_act_nack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK",
+		gsm_lchan_name(msg->lchan));
+
+	/* BTS has rejected channel activation ?!? */
+	if (dh->ie_chan != RSL_IE_CHAN_NR)
+		return -EINVAL;
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) {
+		const u_int8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE);
+		print_rsl_cause(LOGL_ERROR, cause,
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+		if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC)
+			rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+	} else
+		rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL);
+
+	lchan_free(msg->lchan);
+	return 0;
+}
+
+/* Chapter 8.4.4: Connection Failure Indication */
+static int rsl_rx_conn_fail(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	/* FIXME: print which channel */
+	LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING ",
+	     gsm_lchan_name(msg->lchan));
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE),
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+	LOGPC(DRSL, LOGL_NOTICE, "\n");
+	/* FIXME: only free it after channel release ACK */
+	counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rf_fail);
+	return rsl_rf_chan_release(msg->lchan, 1);
+}
+
+static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru,
+				const char *prefix)
+{
+	DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ",
+		prefix, rxlev2dbm(mru->full.rx_lev),
+		prefix, rxlev2dbm(mru->sub.rx_lev));
+	DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ",
+		prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual);
+}
+
+static void print_meas_rep(struct gsm_meas_rep *mr)
+{
+	int i;
+
+	DEBUGP(DMEAS, "MEASUREMENT RESULT NR=%d ", mr->nr);
+
+	if (mr->flags & MEAS_REP_F_DL_DTX)
+		DEBUGPC(DMEAS, "DTXd ");
+
+	print_meas_rep_uni(&mr->ul, "ul");
+	DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power);
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset);
+
+	if (mr->flags & MEAS_REP_F_MS_L1) {
+		DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr);
+		DEBUGPC(DMEAS, "L1_FPC=%u ",
+			mr->flags & MEAS_REP_F_FPC ? 1 : 0);
+		DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta);
+	}
+
+	if (mr->flags & MEAS_REP_F_UL_DTX)
+		DEBUGPC(DMEAS, "DTXu ");
+	if (mr->flags & MEAS_REP_F_BA1)
+		DEBUGPC(DMEAS, "BA1 ");
+	if (!(mr->flags & MEAS_REP_F_DL_VALID))
+		DEBUGPC(DMEAS, "NOT VALID ");
+	else
+		print_meas_rep_uni(&mr->dl, "dl");
+
+	DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell);
+	if (mr->num_cell == 7)
+		return;
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n",
+			mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+	}
+}
+
+static int rsl_rx_meas_res(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+	struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan);
+	u_int8_t len;
+	const u_int8_t *val;
+	int rc;
+
+	/* check if this channel is actually active */
+	/* FIXME: maybe this check should be way more generic/centralized */
+	if (msg->lchan->state != LCHAN_S_ACTIVE) {
+		LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n",
+			gsm_lchan_name(msg->lchan));
+		return 0;
+	}
+
+	memset(mr, 0, sizeof(*mr));
+	mr->lchan = msg->lchan;
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) ||
+	    !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) ||
+	    !TLVP_PRESENT(&tp, RSL_IE_BS_POWER))
+		return -EIO;
+
+	/* Mandatory Parts */
+	mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR);
+
+	len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS);
+	val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS);
+	if (len >= 3) {
+		if (val[0] & 0x40)
+			mr->flags |= MEAS_REP_F_DL_DTX;
+		mr->ul.full.rx_lev = val[0] & 0x3f;
+		mr->ul.sub.rx_lev = val[1] & 0x3f;
+		mr->ul.full.rx_qual = val[2]>>3 & 0x7;
+		mr->ul.sub.rx_qual = val[2] & 0x7;
+	}
+
+	mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+
+	/* Optional Parts */
+	if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET))
+		mr->ms_timing_offset =
+			*TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET);
+
+	if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) {
+		val = TLVP_VAL(&tp, RSL_IE_L1_INFO);
+		mr->flags |= MEAS_REP_F_MS_L1;
+		mr->ms_l1.pwr = ms_pwr_dbm(msg->trx->bts->band, val[0] >> 3);
+		if (val[0] & 0x04)
+			mr->flags |= MEAS_REP_F_FPC;
+		mr->ms_l1.ta = val[1];
+	}
+	if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+		msg->l3h = (u_int8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+		rc = gsm48_parse_meas_rep(mr, msg);
+		if (rc < 0)
+			return rc;
+	}
+
+	print_meas_rep(mr);
+
+	send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr);
+
+	return 0;
+}
+
+/* Chapter 8.4.7 */
+static int rsl_rx_hando_det(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan));
+
+	rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+		DEBUGPC(DRSL, "access delay = %u\n",
+			*TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY));
+	else
+		DEBUGPC(DRSL, "\n");
+
+	send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL);
+
+	return 0;
+}
+
+static int abis_rsl_rx_dchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+
+	msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr);
+	ts_name = gsm_lchan_name(msg->lchan);
+
+	switch (rslh->c.msg_type) {
+	case RSL_MT_CHAN_ACTIV_ACK:
+		DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name);
+		rc = rsl_rx_chan_act_ack(msg);
+		break;
+	case RSL_MT_CHAN_ACTIV_NACK:
+		rc = rsl_rx_chan_act_nack(msg);
+		break;
+	case RSL_MT_CONN_FAIL:
+		rc = rsl_rx_conn_fail(msg);
+		break;
+	case RSL_MT_MEAS_RES:
+		rc = rsl_rx_meas_res(msg);
+		break;
+	case RSL_MT_HANDO_DET:
+		rc = rsl_rx_hando_det(msg);
+		break;
+	case RSL_MT_RF_CHAN_REL_ACK:
+		rc = rsl_rx_rf_chan_rel_ack(msg->lchan);
+		break;
+	case RSL_MT_MODE_MODIFY_ACK:
+		DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name);
+		break;
+	case RSL_MT_MODE_MODIFY_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_PDCH_ACT_ACK:
+		DEBUGPC(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name);
+		msg->lchan->ts->flags |= TS_F_PDCH_MODE;
+		break;
+	case RSL_MT_IPAC_PDCH_ACT_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_PDCH_DEACT_ACK:
+		DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name);
+		msg->lchan->ts->flags &= ~TS_F_PDCH_MODE;
+		break;
+	case RSL_MT_IPAC_PDCH_DEACT_NACK:
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name);
+		break;
+	case RSL_MT_PHY_CONTEXT_CONF:
+	case RSL_MT_PREPROC_MEAS_RES:
+	case RSL_MT_TALKER_DET:
+	case RSL_MT_LISTENER_DET:
+	case RSL_MT_REMOTE_CODEC_CONF_REP:
+	case RSL_MT_MR_CODEC_MOD_ACK:
+	case RSL_MT_MR_CODEC_MOD_NACK:
+	case RSL_MT_MR_CODEC_MOD_PER:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan "
+			"msg 0x%02x\n", ts_name, rslh->c.msg_type);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n",
+			ts_name, rslh->c.msg_type);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int rsl_rx_error_rep(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	struct tlv_parsed tp;
+
+	LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(msg->trx));
+
+	rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh));
+
+	if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE),
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	return 0;
+}
+
+static int abis_rsl_rx_trx(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+
+	switch (rslh->msg_type) {
+	case RSL_MT_ERROR_REPORT:
+		rc = rsl_rx_error_rep(msg);
+		break;
+	case RSL_MT_RF_RES_IND:
+		/* interference on idle channels of TRX */
+		//DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(msg->trx));
+		break;
+	case RSL_MT_OVERLOAD:
+		/* indicate CCCH / ACCH / processor overload */
+		LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n",
+		     gsm_trx_name(msg->trx));
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message "
+			"type 0x%02x\n", gsm_trx_name(msg->trx), rslh->msg_type);
+		return -EINVAL;
+	}
+	return rc;
+}
+
+/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */
+static void t3101_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	rsl_rf_chan_release(lchan, 1);
+}
+
+/* If T3111 expires, we will send the RF Channel Request */
+static void t3111_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	rsl_rf_chan_release(lchan, 0);
+}
+
+#define GSM48_LEN2PLEN(a)	(((a) << 2) | 1)
+
+/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */
+static int rsl_send_imm_ass_rej(struct gsm_bts *bts,
+				unsigned int num_req_refs,
+				struct gsm48_req_ref *rqd_refs,
+				uint8_t wait_ind)
+{
+	uint8_t buf[GSM_MACBLOCK_LEN];
+	struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf;
+
+	/* create IMMEDIATE ASSIGN REJECT 04.08 message */
+	memset(iar, 0, sizeof(*iar));
+	iar->proto_discr = GSM48_PDISC_RR;
+	iar->msg_type = GSM48_MT_RR_IMM_ASS;
+	iar->page_mode = GSM48_PM_SAME;
+
+	memcpy(&iar->req_ref1, &rqd_refs[0], sizeof(iar->req_ref1));
+	iar->wait_ind1 = wait_ind;
+
+	if (num_req_refs >= 2)
+		memcpy(&iar->req_ref2, &rqd_refs[1], sizeof(iar->req_ref2));
+	else
+		memcpy(&iar->req_ref2, &rqd_refs[0], sizeof(iar->req_ref2));
+	iar->wait_ind2 = wait_ind;
+
+	if (num_req_refs >= 3)
+		memcpy(&iar->req_ref3, &rqd_refs[2], sizeof(iar->req_ref3));
+	else
+		memcpy(&iar->req_ref3, &rqd_refs[0], sizeof(iar->req_ref3));
+	iar->wait_ind3 = wait_ind;
+
+	if (num_req_refs >= 4)
+		memcpy(&iar->req_ref4, &rqd_refs[3], sizeof(iar->req_ref4));
+	else
+		memcpy(&iar->req_ref4, &rqd_refs[0], sizeof(iar->req_ref4));
+	iar->wait_ind4 = wait_ind;
+
+	return rsl_imm_assign_cmd(bts, sizeof(iar), (uint8_t *) iar);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_chan_rqd(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->trx->bts;
+	struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
+	struct gsm48_req_ref *rqd_ref;
+	enum gsm_chan_t lctype;
+	enum gsm_chreq_reason_t chreq_reason;
+	struct gsm_lchan *lchan;
+	u_int8_t rqd_ta;
+	int is_lu;
+
+	u_int16_t arfcn;
+	u_int8_t ts_number, subch;
+
+	/* parse request reference to be used in immediate assign */
+	if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE)
+		return -EINVAL;
+
+	rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+
+	/* parse access delay and use as TA */
+	if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY)
+		return -EINVAL;
+	rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+
+	/* determine channel type (SDCCH/TCH_F/TCH_H) based on
+	 * request reference RA */
+	lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra);
+	chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci);
+
+	counter_inc(bts->network->stats.chreq.total);
+
+	/*
+	 * We want LOCATION UPDATES to succeed and will assign a TCH
+	 * if we have no SDCCH available.
+	 */
+	is_lu = !!(chreq_reason == GSM_CHREQ_REASON_LOCATION_UPD);
+
+	/* check availability / allocate channel */
+	lchan = lchan_alloc(bts, lctype, is_lu);
+	if (!lchan) {
+		LOGP(DRSL, LOGL_NOTICE, "BTS %d CHAN RQD: no resources for %s 0x%x\n",
+		     msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra);
+		counter_inc(bts->network->stats.chreq.no_channel);
+		/* FIXME gather multiple CHAN RQD and reject up to 4 at the same time */
+		if (bts->network->T3122)
+			rsl_send_imm_ass_rej(bts, 1, rqd_ref, bts->network->T3122 & 0xff);
+		return -ENOMEM;
+	}
+
+	if (lchan->state != LCHAN_S_NONE)
+		LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel "
+		     "in state %s\n", gsm_lchan_name(lchan),
+		     gsm_lchans_name(lchan->state));
+	rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+	/* save the RACH data as we need it after the CHAN ACT ACK */
+	lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref);
+	if (!lchan->rqd_ref) {
+		LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n");
+		lchan_free(lchan);
+		return -ENOMEM;
+	}
+
+	memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref));
+	lchan->rqd_ta = rqd_ta;
+
+	ts_number = lchan->ts->nr;
+	arfcn = lchan->ts->trx->arfcn;
+	subch = lchan->nr;
+	
+	lchan->encr.alg_id = RSL_ENC_ALG_A5(0);	/* no encryption */
+	lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+	lchan->bs_power = 0; /* 0dB reduction, output power = Pn */
+	lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+	lchan->tch_mode = GSM48_CMODE_SIGN;
+
+	/* FIXME: Start another timer or assume the BTS sends a ACK/NACK? */
+	rsl_chan_activate_lchan(lchan, 0x00, rqd_ta, 0);
+
+	DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s "
+		"r=%s ra=0x%02x\n", gsm_lchan_name(lchan), arfcn, subch,
+		gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason),
+		rqd_ref->ra);
+	return 0;
+}
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	u_int8_t buf[GSM_MACBLOCK_LEN];
+	struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf;
+
+	/* create IMMEDIATE ASSIGN 04.08 messge */
+	memset(ia, 0, sizeof(*ia));
+	/* we set ia->l2_plen once we know the length of the MA below */
+	ia->proto_discr = GSM48_PDISC_RR;
+	ia->msg_type = GSM48_MT_RR_IMM_ASS;
+	ia->page_mode = GSM48_PM_SAME;
+	gsm48_lchan2chan_desc(&ia->chan_desc, lchan);
+
+	/* use request reference extracted from CHAN_RQD */
+	memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref));
+	ia->timing_advance = lchan->rqd_ta;
+	if (!lchan->ts->hopping.enabled) {
+		ia->mob_alloc_len = 0;
+	} else {
+		ia->mob_alloc_len = lchan->ts->hopping.ma_len;
+		memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len);
+	}
+	/* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */
+	ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len);
+
+	/* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */
+	lchan->T3101.cb = t3101_expired;
+	lchan->T3101.data = lchan;
+	bsc_schedule_timer(&lchan->T3101, bts->network->T3101, 0);
+
+	/* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */
+	return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (u_int8_t *) ia);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_ccch_load(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	u_int16_t pg_buf_space;
+	u_int16_t rach_slot_count = -1;
+	u_int16_t rach_busy_count = -1;
+	u_int16_t rach_access_count = -1;
+
+	switch (rslh->data[0]) {
+	case RSL_IE_PAGING_LOAD:
+		pg_buf_space = rslh->data[1] << 8 | rslh->data[2];
+		if (is_ipaccess_bts(msg->trx->bts) && pg_buf_space == 0xffff) {
+			/* paging load below configured threshold, use 50 as default */
+			pg_buf_space = 50;
+		}
+		paging_update_buffer_space(msg->trx->bts, pg_buf_space);
+		break;
+	case RSL_IE_RACH_LOAD:
+		if (msg->data_len >= 7) {
+			rach_slot_count = rslh->data[2] << 8 | rslh->data[3];
+			rach_busy_count = rslh->data[4] << 8 | rslh->data[5];
+			rach_access_count = rslh->data[6] << 8 | rslh->data[7];
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int abis_rsl_rx_cchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr);
+
+	switch (rslh->c.msg_type) {
+	case RSL_MT_CHAN_RQD:
+		/* MS has requested a channel on the RACH */
+		rc = rsl_rx_chan_rqd(msg);
+		break;
+	case RSL_MT_CCCH_LOAD_IND:
+		/* current load on the CCCH */
+		rc = rsl_rx_ccch_load(msg);
+		break;
+	case RSL_MT_DELETE_IND:
+		/* CCCH overloaded, IMM_ASSIGN was dropped */
+	case RSL_MT_CBCH_LOAD_IND:
+		/* current load on the CBCH */
+		LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message "
+			"type 0x%02x\n", rslh->c.msg_type);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type "
+			"0x%02x\n", rslh->c.msg_type);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int rsl_rx_rll_err_ind(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	u_int8_t *rlm_cause = rllh->data;
+
+	LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s\n",
+		gsm_lchan_name(msg->lchan),
+		rsl_rlm_cause_name(rlm_cause[1]));
+
+	rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
+
+	if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED) {
+		counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rll_err);
+		return rsl_rf_chan_release(msg->lchan, 1);
+	}
+
+	return 0;
+}
+
+static void rsl_handle_release(struct gsm_lchan *lchan)
+{
+	int sapi;
+	struct gsm_bts *bts;
+
+	/* maybe we have only brought down one RLL */
+	if (lchan->state != LCHAN_S_REL_REQ)
+		return;
+
+	for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+		if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+			continue;
+		LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n",
+		     gsm_lchan_name(lchan), sapi);
+		return;
+	}
+
+
+
+	/* wait a bit to send the RF Channel Release */
+	lchan->T3111.cb = t3111_expired;
+	lchan->T3111.data = lchan;
+	bts = lchan->ts->trx->bts;
+	bsc_schedule_timer(&lchan->T3111, bts->network->T3111, 0);
+}
+
+/*	ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST
+	0x02, 0x06,
+	0x01, 0x20,
+	0x02, 0x00,
+	0x0b, 0x00, 0x0f, 0x05, 0x08, ... */
+
+static int abis_rsl_rx_rll(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+	u_int8_t sapi = rllh->link_id & 7;
+
+	msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr);
+	ts_name = gsm_lchan_name(msg->lchan);
+	DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi);
+	
+	switch (rllh->c.msg_type) {
+	case RSL_MT_DATA_IND:
+		DEBUGPC(DRLL, "DATA INDICATION\n");
+		if (msgb_l2len(msg) >
+		    sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+		    rllh->data[0] == RSL_IE_L3_INFO) {
+			msg->l3h = &rllh->data[3];
+			return gsm0408_rcvmsg(msg, rllh->link_id);
+		}
+		break;
+	case RSL_MT_EST_IND:
+		DEBUGPC(DRLL, "ESTABLISH INDICATION\n");
+		/* lchan is established, stop T3101 */
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS;
+		bsc_del_timer(&msg->lchan->T3101);
+		if (msgb_l2len(msg) >
+		    sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+		    rllh->data[0] == RSL_IE_L3_INFO) {
+			msg->l3h = &rllh->data[3];
+			return gsm0408_rcvmsg(msg, rllh->link_id);
+		}
+		break;
+	case RSL_MT_EST_CONF:
+		DEBUGPC(DRLL, "ESTABLISH CONFIRM\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET;
+		rll_indication(msg->lchan, rllh->link_id,
+				  BSC_RLLR_IND_EST_CONF);
+		break;
+	case RSL_MT_REL_IND:
+		/* BTS informs us of having received  DISC from MS */
+		DEBUGPC(DRLL, "RELEASE INDICATION\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+		rll_indication(msg->lchan, rllh->link_id,
+				  BSC_RLLR_IND_REL_IND);
+		rsl_handle_release(msg->lchan);
+		rsl_lchan_rll_release(msg->lchan, rllh->link_id);
+		break;
+	case RSL_MT_REL_CONF:
+		/* BTS informs us of having received UA from MS,
+		 * in response to DISC that we've sent earlier */
+		DEBUGPC(DRLL, "RELEASE CONFIRMATION\n");
+		msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+		rsl_handle_release(msg->lchan);
+		rsl_lchan_rll_release(msg->lchan, rllh->link_id);
+		break;
+	case RSL_MT_ERROR_IND:
+		rc = rsl_rx_rll_err_ind(msg);
+		break;
+	case RSL_MT_UNIT_DATA_IND:
+		LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+	}
+	return rc;
+}
+
+static u_int8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan)
+{
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x00;
+		case GSM_LCHAN_TCH_H:
+			return 0x03;
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x01;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x02;
+		case GSM_LCHAN_TCH_H:
+			return 0x05;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for "
+		"tch_mode == 0x%02x\n", lchan->tch_mode);
+	return 0;
+}
+
+static u_int8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan)
+{
+	struct gsm_network *net = lchan->ts->trx->bts->network;
+
+	/* allow to hardcode the rtp payload */
+	if (net->hardcoded_rtp_payload != 0)
+		return net->hardcoded_rtp_payload;
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_GSM_HALF;
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_GSM_EFR;
+		/* there's no half-rate EFR */
+		default:
+			break;
+		}
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_AMR_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_AMR_HALF;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for "
+		"tch_mode == 0x%02x\n & lchan_type == %d",
+		lchan->tch_mode, lchan->type);
+	return 0;
+}
+
+/* ip.access specific RSL extensions */
+static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv)
+{
+	struct in_addr ip;
+	u_int16_t port, conn_id;
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) {
+		ip.s_addr = *((u_int32_t *) TLVP_VAL(tv, RSL_IE_IPAC_LOCAL_IP));
+		DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip));
+		lchan->abis_ip.bound_ip = ntohl(ip.s_addr);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) {
+		port = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_LOCAL_PORT));
+		port = ntohs(port);
+		DEBUGPC(DRSL, "LOCAL_PORT=%u ", port);
+		lchan->abis_ip.bound_port = port;
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) {
+		conn_id = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_CONN_ID));
+		conn_id = ntohs(conn_id);
+		DEBUGPC(DRSL, "CON_ID=%u ", conn_id);
+		lchan->abis_ip.conn_id = conn_id;
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) {
+		lchan->abis_ip.rtp_payload2 =
+				*TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2);
+		DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ",
+			lchan->abis_ip.rtp_payload2);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) {
+		lchan->abis_ip.speech_mode =
+				*TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE);
+		DEBUGPC(DRSL, "speech_mode=0x%02x ",
+			lchan->abis_ip.speech_mode);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) {
+		ip.s_addr = *((u_int32_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_IP));
+		DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip));
+		lchan->abis_ip.connect_ip = ntohl(ip.s_addr);
+	}
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) {
+		port = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_PORT));
+		port = ntohs(port);
+		DEBUGPC(DRSL, "REMOTE_PORT=%u ", port);
+		lchan->abis_ip.connect_port = port;
+	}
+}
+
+int rsl_ipacc_crcx(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IPAC_CRCX);
+	dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	/* 0x1- == receive-only, 0x-1 == EFR codec */
+	lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+
+	DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n",
+		gsm_lchan_name(lchan), lchan->abis_ip.speech_mode,
+		lchan->abis_ip.rtp_payload);
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip, u_int16_t port,
+		   u_int8_t rtp_payload2)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int32_t *att_ip;
+	struct in_addr ia;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, RSL_MT_IPAC_MDCX);
+	dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+	dh->chan_nr = lchan2chan_nr(lchan);
+
+	/* we need to store these now as MDCX_ACK does not return them :( */
+	lchan->abis_ip.rtp_payload2 = rtp_payload2;
+	lchan->abis_ip.connect_port = port;
+	lchan->abis_ip.connect_ip = ip;
+
+	/* 0x0- == both directions, 0x-1 == EFR codec */
+	lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan);
+	lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d "
+		"CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan),
+		inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2,
+		lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode);
+
+	msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+	msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
+	att_ip = (u_int32_t *) msgb_put(msg, sizeof(ip));
+	*att_ip = ia.s_addr;
+	msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port);
+	msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+	msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+	if (rtp_payload2)
+		msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2);
+	
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* tell BTS to connect RTP stream to our local RTP socket */
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan)
+{
+	struct rtp_socket *rs = lchan->abis_ip.rtp_socket;
+	int rc;
+
+	rc = rsl_ipacc_mdcx(lchan, ntohl(rs->rtp.sin_local.sin_addr.s_addr),
+				ntohs(rs->rtp.sin_local.sin_port),
+			/* FIXME: use RTP payload of bound socket, not BTS*/
+				lchan->abis_ip.rtp_payload2);
+
+	return rc;
+}
+
+int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int8_t msg_type;
+
+	if (act)
+		msg_type = RSL_MT_IPAC_PDCH_ACT;
+	else
+		msg_type = RSL_MT_IPAC_PDCH_DEACT;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+	init_dchan_hdr(dh, msg_type);
+	dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+	dh->chan_nr = ts2chan_nr(ts, 0);
+
+	DEBUGP(DRSL, "%s IPAC_PDCH_%sACT\n", gsm_ts_name(ts),
+		act ? "" : "DE");
+
+	msg->trx = ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	/* the BTS has acknowledged a local bind, it now tells us the IP
+	* address and port number to which it has bound the given logical
+	* channel */
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) ||
+	    !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) ||
+	    !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) {
+		LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing");
+		return -EINVAL;
+	}
+
+	ipac_parse_rtp(lchan, &tv);
+
+	dispatch_signal(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	/* the BTS has acknowledged a remote connect request and
+	 * it now tells us the IP address and port number to which it has
+	 * connected the given logical channel */
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+	ipac_parse_rtp(lchan, &tv);
+	dispatch_signal(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+	struct tlv_parsed tv;
+
+	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+	if (TLVP_PRESENT(&tv, RSL_IE_CAUSE))
+		print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE),
+				TLVP_LEN(&tv, RSL_IE_CAUSE));
+
+	dispatch_signal(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	char *ts_name;
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr);
+	ts_name = gsm_lchan_name(msg->lchan);
+	
+	switch (rllh->c.msg_type) {
+	case RSL_MT_IPAC_CRCX_ACK:
+		DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name);
+		rc = abis_rsl_rx_ipacc_crcx_ack(msg);
+		break;
+	case RSL_MT_IPAC_CRCX_NACK:
+		/* somehow the BTS was unable to bind the lchan to its local
+		 * port?!? */
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_MDCX_ACK:
+		/* the BTS tells us that a connect operation was successful */
+		DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name);
+		rc = abis_rsl_rx_ipacc_mdcx_ack(msg);
+		break;
+	case RSL_MT_IPAC_MDCX_NACK:
+		/* somehow the BTS was unable to connect the lchan to a remote
+		 * port */
+		LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name);
+		break;
+	case RSL_MT_IPAC_DLCX_IND:
+		DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name);
+		rc = abis_rsl_rx_ipacc_dlcx_ind(msg);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n",
+			rllh->c.msg_type);
+		break;
+	}
+	DEBUGPC(DRSL, "\n");
+
+	return rc;
+}
+
+
+/* Entry-point where L2 RSL from BTS enters */
+int abis_rsl_rcvmsg(struct msgb *msg)
+{
+	struct abis_rsl_common_hdr *rslh;
+	int rc = 0;
+
+	if (!msg) {
+		DEBUGP(DRSL, "Empty RSL msg?..\n");
+		return -1;
+	}
+
+	if (msgb_l2len(msg) < sizeof(*rslh)) {
+		DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg));
+		return -1;
+	}
+
+	rslh = msgb_l2(msg);
+
+	switch (rslh->msg_discr & 0xfe) {
+	case ABIS_RSL_MDISC_RLL:
+		rc = abis_rsl_rx_rll(msg);
+		break;
+	case ABIS_RSL_MDISC_DED_CHAN:
+		rc = abis_rsl_rx_dchan(msg);
+		break;
+	case ABIS_RSL_MDISC_COM_CHAN:
+		rc = abis_rsl_rx_cchan(msg);
+		break;
+	case ABIS_RSL_MDISC_TRX:
+		rc = abis_rsl_rx_trx(msg);
+		break;
+	case ABIS_RSL_MDISC_LOC:
+		LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n",
+			rslh->msg_discr);
+		break;
+	case ABIS_RSL_MDISC_IPACCESS:
+		rc = abis_rsl_rx_ipacc(msg);
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator "
+			"0x%02x\n", rslh->msg_discr);
+		return -EINVAL;
+	}
+	msgb_free(msg);
+	return rc;
+}
+
+/* From Table 10.5.33 of GSM 04.08 */
+int rsl_number_of_paging_subchannels(struct gsm_bts *bts)
+{
+	if (bts->si_common.chan_desc.ccch_conf == RSL_BCCH_CCCH_CONF_1_C) {
+		return MAX(1, (3 - bts->si_common.chan_desc.bs_ag_blks_res))
+			* (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+	} else {
+		return (9 - bts->si_common.chan_desc.bs_ag_blks_res)
+			* (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+	}
+}
+
+int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
+		       uint8_t cb_command, const uint8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *cb_cmd;
+
+	cb_cmd = rsl_msgb_alloc();
+	if (!cb_cmd)
+		return -1;
+
+	dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof*dh);
+	init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD);
+	dh->chan_nr = RSL_CHAN_SDCCH4_ACCH; /* TODO: check the chan config */
+
+	msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, cb_command);
+	msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data);
+
+	cb_cmd->trx = bts->c0;
+
+	return abis_rsl_sendmsg(cb_cmd);
+}
diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c
new file mode 100644
index 0000000..0f09aec
--- /dev/null
+++ b/src/libbsc/bsc_api.c
@@ -0,0 +1,675 @@
+/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */
+
+/* (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/handover.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocore/talloc.h>
+
+#define GSM0808_T10_VALUE    6, 0
+
+static LLIST_HEAD(sub_connections);
+
+static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind);
+static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id);
+static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct  gsm_lchan *lchan);
+
+/* GSM 08.08 3.2.2.33 */
+static u_int8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
+{
+	u_int8_t channel_mode = 0, channel = 0;
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+	case GSM48_CMODE_SPEECH_EFR:
+	case GSM48_CMODE_SPEECH_AMR:
+		channel_mode = 0x9;
+		break;
+	case GSM48_CMODE_SIGN:
+		channel_mode = 0x8;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+		channel_mode = 0xe;
+		break;
+	case GSM48_CMODE_DATA_12k0:
+		channel_mode = 0xb;
+		break;
+	case GSM48_CMODE_DATA_6k0:
+		channel_mode = 0xc;
+		break;
+	case GSM48_CMODE_DATA_3k6:
+		channel_mode = 0xd;
+		break;
+	}
+
+	switch (lchan->type) {
+	case GSM_LCHAN_NONE:
+		channel = 0x0;
+		break;
+	case GSM_LCHAN_SDCCH:
+		channel = 0x1;
+		break;
+	case GSM_LCHAN_TCH_F:
+		channel = 0x8;
+		break;
+	case GSM_LCHAN_TCH_H:
+		channel = 0x9;
+		break;
+	case GSM_LCHAN_UNKNOWN:
+		LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan);
+		break;
+	}
+
+	return channel_mode << 4 | channel;
+}
+
+static u_int8_t chan_mode_to_speech(struct gsm_lchan *lchan)
+{
+	int mode = 0;
+
+	switch (lchan->tch_mode) {
+	case GSM48_CMODE_SPEECH_V1:
+		mode = 1;
+		break;
+	case GSM48_CMODE_SPEECH_EFR:
+		mode = 0x11;
+		break;
+	case GSM48_CMODE_SPEECH_AMR:
+		mode = 0x21;
+		break;
+	case GSM48_CMODE_SIGN:
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_6k0:
+	case GSM48_CMODE_DATA_3k6:
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Using non speech mode: %d\n", mode);
+		return 0;
+		break;
+	}
+
+	/* assume to always do AMR HR on any TCH type */
+	if (lchan->type == GSM_LCHAN_TCH_H ||
+	    lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		mode |= 0x4;
+
+        return mode;
+}
+
+static void assignment_t10_timeout(void *_conn)
+{
+	struct bsc_api *api;
+	struct gsm_subscriber_connection *conn =
+		(struct gsm_subscriber_connection *) _conn;
+
+	LOGP(DMSC, LOGL_ERROR, "Assigment T10 timeout on %p\n", conn);
+
+	/* normal release on the secondary channel */
+	lchan_release(conn->secondary_lchan, 0, 1);
+	conn->secondary_lchan = NULL;
+
+	/* inform them about the failure */
+	api = conn->bts->network->bsc_api;
+	api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+}
+
+/*
+ * Start a new assignment and make sure that it is completed within T10 either
+ * positively, negatively or by the timeout.
+ *
+ *  1.) allocate a new lchan
+ *  2.) copy the encryption key and other data from the
+ *      old to the new channel.
+ *  3.) RSL Channel Activate this channel and wait
+ *
+ * -> Signal handler for the LCHAN
+ *  4.) Send GSM 04.08 assignment command to the MS
+ *
+ * -> Assignment Complete/Assignment Failure
+ *  5.) Release the SDCCH, continue signalling on the new link
+ */
+static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+	struct gsm_lchan *new_lchan;
+	int chan_type;
+
+	chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
+
+	new_lchan = lchan_alloc(conn->bts, chan_type, 0);
+
+	if (!new_lchan) {
+		LOGP(DMSC, LOGL_NOTICE, "No free channel.\n");
+		return -1;
+	}
+
+	/* copy old data to the new channel */
+	memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr));
+	new_lchan->ms_power = conn->lchan->ms_power;
+	new_lchan->bs_power = conn->lchan->bs_power;
+
+	/* copy new data to it */
+	new_lchan->tch_mode = chan_mode;
+	new_lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+
+	/* handle AMR correctly */
+	if (chan_mode == GSM48_CMODE_SPEECH_AMR) {
+		new_lchan->mr_conf.ver = 1;
+		new_lchan->mr_conf.icmi = 1;
+		new_lchan->mr_conf.m5_90 = 1;
+	}
+
+	if (rsl_chan_activate_lchan(new_lchan, 0x1, 0, 0) < 0) {
+		LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+		lchan_free(new_lchan);
+		return -1;
+	}
+
+	/* remember that we have the channel */
+	conn->secondary_lchan = new_lchan;
+	new_lchan->conn = conn;
+
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+	return 0;
+}
+
+struct gsm_subscriber_connection *subscr_con_allocate(struct gsm_lchan *lchan)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = talloc_zero(lchan->ts->trx->bts->network, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	/* Configure the time and start it so it will be closed */
+	conn->lchan = lchan;
+	conn->bts = lchan->ts->trx->bts;
+	lchan->conn = conn;
+	llist_add_tail(&conn->entry, &sub_connections);
+	return conn;
+}
+
+/* TODO: move subscriber put here... */
+void subscr_con_free(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+
+	if (conn->subscr) {
+		subscr_put(conn->subscr);
+		conn->subscr = NULL;
+	}
+
+
+	if (conn->ho_lchan) {
+		LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n");
+		conn->ho_lchan->conn = NULL;
+	}
+
+	if (conn->lchan) {
+		LOGP(DNM, LOGL_ERROR, "The lchan should have been cleared.\n");
+		conn->lchan->conn = NULL;
+	}
+
+	if (conn->secondary_lchan) {
+		LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n");
+		conn->secondary_lchan->conn = NULL;
+	}
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+}
+
+int bsc_api_init(struct gsm_network *network, struct bsc_api *api)
+{
+	network->bsc_api = api;
+	return 0;
+}
+
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn,
+			struct msgb *msg, int link_id, int allow_sach)
+{
+	uint8_t sapi;
+
+
+	if (!conn->lchan) {
+		LOGP(DMSC, LOGL_ERROR,
+		     "Called submit dtap without an lchan.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	sapi = link_id & 0x7;
+	msg->lchan = conn->lchan;
+	msg->trx = msg->lchan->ts->trx;
+
+	/* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */
+	if (allow_sach && sapi != 0) {
+		if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H)
+			link_id |= 0x40;
+	}
+
+	msg->l3h = msg->data;
+	if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) {
+		OBSC_LINKID_CB(msg) = link_id;
+		if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) {
+			msgb_free(msg);
+			send_sapi_reject(conn, link_id);
+			return -1;
+		}
+		return 0;
+	} else {
+		return rsl_data_request(msg, link_id);
+	}
+}
+
+/**
+ * Send a GSM08.08 Assignment Request. Right now this does not contain the
+ * audio codec type or the allowed rates for the config. It is assumed that
+ * this is for audio handling and that when we have a TCH it is capable of
+ * handling the audio codec. On top of that it is assumed that we are using
+ * AMR 5.9 when assigning a TCH/H.
+ */
+int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+	struct bsc_api *api;
+	api = conn->bts->network->bsc_api;
+
+	if (conn->lchan->type == GSM_LCHAN_SDCCH) {
+		if (handle_new_assignment(conn, chan_mode, full_rate) != 0)
+			goto error;
+	} else {
+		LOGP(DMSC, LOGL_NOTICE,
+			"Sending ChanModify for speech %d %d\n", chan_mode, full_rate);
+		if (chan_mode == GSM48_CMODE_SPEECH_AMR) {
+			conn->lchan->mr_conf.ver = 1;
+			conn->lchan->mr_conf.icmi = 1;
+			conn->lchan->mr_conf.m5_90 = 1;
+		}
+
+		gsm48_lchan_modify(conn->lchan, chan_mode);
+	}
+
+	/* we will now start the timer to complete the assignment */
+	conn->T10.cb = assignment_t10_timeout;
+	conn->T10.data = conn;
+	bsc_schedule_timer(&conn->T10, GSM0808_T10_VALUE);
+	return 0;
+
+error:
+	api->assign_fail(conn, 0, NULL);
+	return -1;
+}
+
+int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len,
+		 uint8_t *mi, int chan_type)
+{
+	return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type);
+}
+
+static void handle_ass_compl(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	struct bsc_api *api = conn->bts->network->bsc_api;
+
+	if (conn->secondary_lchan != msg->lchan) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment Compl should occur on second lchan.\n");
+		return;
+	}
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment Compl invalid: %lu\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		return;
+	}
+
+	/* swap channels */
+	bsc_del_timer(&conn->T10);
+
+	lchan_release(conn->lchan, 0, 1);
+	conn->lchan = conn->secondary_lchan;
+	conn->secondary_lchan = NULL;
+
+	if (is_ipaccess_bts(conn->bts) && conn->lchan->tch_mode != GSM48_CMODE_SIGN)
+		rsl_ipacc_crcx(conn->lchan);
+
+	api->assign_compl(conn, gh->data[0],
+			  lchan_to_chosen_channel(conn->lchan),
+			  conn->lchan->encr.alg_id,
+			  chan_mode_to_speech(conn->lchan));
+}
+
+static void handle_ass_fail(struct gsm_subscriber_connection *conn,
+			    struct msgb *msg)
+{
+	struct bsc_api *api = conn->bts->network->bsc_api;
+	uint8_t *rr_failure;
+	struct gsm48_hdr *gh;
+
+
+	if (conn->lchan != msg->lchan) {
+		LOGP(DMSC, LOGL_ERROR, "Assignment failure should occur on primary lchan.\n");
+		return;
+	}
+
+	/* stop the timer and release it */
+	bsc_del_timer(&conn->T10);
+	lchan_release(conn->secondary_lchan, 0, 1);
+	conn->secondary_lchan = NULL;
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "assignemnt failure unhandled: %lu\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		rr_failure = NULL;
+	} else {
+		rr_failure = &gh->data[0];
+	}
+
+	api->assign_fail(conn,
+			 GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE,
+			 rr_failure);
+}
+
+static void dispatch_dtap(struct gsm_subscriber_connection *conn,
+			  uint8_t link_id, struct msgb *msg)
+{
+	struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+	struct gsm48_hdr *gh;
+	uint8_t pdisc;
+	int rc;
+
+	if (msgb_l3len(msg) < sizeof(*gh)) {
+		LOGP(DMSC, LOGL_ERROR, "Message too short for a GSM48 header.\n");
+		return;
+	}
+
+	gh = msgb_l3(msg);
+	pdisc = gh->proto_discr & 0x0f;
+	switch (pdisc) {
+	case GSM48_PDISC_RR:
+		switch (gh->msg_type) {
+		case GSM48_MT_RR_CIPH_M_COMPL:
+			if (api->cipher_mode_compl)
+				return api->cipher_mode_compl(conn, msg,
+						conn->lchan->encr.alg_id);
+			break;
+		case GSM48_MT_RR_ASS_COMPL:
+			handle_ass_compl(conn, msg);
+			break;
+		case GSM48_MT_RR_ASS_FAIL:
+			handle_ass_fail(conn, msg);
+			break;
+		case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
+			bsc_del_timer(&conn->T10);
+			rc = gsm48_rx_rr_modif_ack(msg);
+			if (rc < 0 && api->assign_fail) {
+				api->assign_fail(conn,
+						 GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+						 NULL);
+			} else if (rc >= 0 && api->assign_compl)
+				api->assign_compl(conn, 0,
+						  lchan_to_chosen_channel(conn->lchan),
+						  conn->lchan->encr.alg_id,
+						  chan_mode_to_speech(conn->lchan));
+			return;
+			break;
+		}
+		break;
+	case GSM48_PDISC_MM:
+		break;
+	}
+
+	/* default case */
+	if (api->dtap)
+		api->dtap(conn, link_id, msg);
+}
+
+int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id)
+{
+	int rc;
+	struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+	struct gsm_lchan *lchan;
+
+	lchan = msg->lchan;
+	if (lchan->state != LCHAN_S_ACTIVE) {
+		LOGP(DRSL, LOGL_INFO, "Got data in non active state(%s), "
+		     "discarding.\n", gsm_lchans_name(lchan->state));
+		return -1;
+	}
+
+
+	if (lchan->conn) {
+		dispatch_dtap(lchan->conn, link_id, msg);
+	} else {
+		rc = BSC_API_CONN_POL_REJECT;
+		lchan->conn = subscr_con_allocate(msg->lchan);
+		if (!lchan->conn) {
+			lchan_release(lchan, 0, 0);
+			return -1;
+		}
+
+		rc = api->compl_l3(lchan->conn, msg, 0);
+
+		if (rc != BSC_API_CONN_POL_ACCEPT) {
+			lchan->conn->lchan = NULL;
+			subscr_con_free(lchan->conn);
+			lchan_release(lchan, 0, 0);
+		}
+	}
+
+	return 0;
+}
+
+int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
+			const uint8_t *key, int len, int include_imeisv)
+{
+	if (cipher > 0 && key == NULL) {
+		LOGP(DRSL, LOGL_ERROR, "Need to have an encrytpion key.\n");
+		return -1;
+	}
+
+	if (len > MAX_A5_KEY_LEN) {
+		LOGP(DRSL, LOGL_ERROR, "The key is too long: %d\n", len);
+		return -1;
+	}
+
+	conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher);
+	if (key) {
+		conn->lchan->encr.key_len = len;
+		memcpy(conn->lchan->encr.key, key, len);
+	}
+
+	return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv);
+}
+
+/*
+ * Release all occupied RF Channels but stay around for more.
+ */
+int gsm0808_clear(struct gsm_subscriber_connection *conn)
+{
+	if (conn->ho_lchan)
+		bsc_clear_handover(conn, 1);
+
+	if (conn->secondary_lchan)
+		lchan_release(conn->secondary_lchan, 0, 1);
+
+	if (conn->lchan)
+		lchan_release(conn->lchan, 1, 0);
+
+	conn->lchan = NULL;
+	conn->secondary_lchan = NULL;
+	conn->ho_lchan = NULL;
+	conn->bts = NULL;
+
+	bsc_del_timer(&conn->T10);
+
+	return 0;
+}
+
+static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id)
+{
+	struct bsc_api *api;
+
+	if (!conn)
+		return;
+
+	api = conn->bts->network->bsc_api;
+	if (!api || !api->sapi_n_reject)
+		return;
+
+	api->sapi_n_reject(conn, link_id);
+}
+
+static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind)
+{
+	struct msgb *msg = _data;
+
+	/*
+	 * There seems to be a small window that the RLL timer can
+	 * fire after a lchan_release call and before the S_CHALLOC_FREED
+	 * is called. Check if a conn is set before proceeding.
+	 */
+	if (!lchan->conn)
+		return;
+
+	switch (rllr_ind) {
+	case BSC_RLLR_IND_EST_CONF:
+		rsl_data_request(msg, OBSC_LINKID_CB(msg));
+		break;
+	case BSC_RLLR_IND_REL_IND:
+	case BSC_RLLR_IND_ERR_IND:
+	case BSC_RLLR_IND_TIMEOUT:
+		send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg));
+		msgb_free(msg);
+		break;
+	}
+}
+
+static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal,
+				   void *handler_data, void *signal_data)
+{
+	struct bsc_api *bsc;
+	struct gsm_lchan *lchan;
+	struct lchan_signal_data *lchan_data;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+
+	lchan_data = signal_data;
+	if (!lchan_data->lchan || !lchan_data->lchan->conn)
+		return 0;
+
+	lchan = lchan_data->lchan;
+	bsc = lchan->ts->trx->bts->network->bsc_api;
+	if (!bsc)
+		return 0;
+
+	switch (signal) {
+	case S_LCHAN_UNEXPECTED_RELEASE:
+		handle_release(lchan->conn, bsc, lchan);
+		break;
+	case S_LCHAN_ACTIVATE_ACK:
+		handle_chan_ack(lchan->conn, bsc, lchan);
+		break;
+	case S_LCHAN_ACTIVATE_NACK:
+		handle_chan_nack(lchan->conn, bsc, lchan);
+		break;
+	}
+
+	return 0;
+}
+
+static void handle_release(struct gsm_subscriber_connection *conn,
+			   struct bsc_api *bsc, struct gsm_lchan *lchan)
+{
+	int destruct = 1;
+
+	if (conn->secondary_lchan == lchan) {
+		bsc_del_timer(&conn->T10);
+		conn->secondary_lchan = NULL;
+
+		bsc->assign_fail(conn,
+				 GSM0808_CAUSE_RADIO_INTERFACE_FAILURE,
+				 NULL);
+	}
+
+	/* clear the connection now */
+	if (bsc->clear_request)
+		destruct = bsc->clear_request(conn, 0);
+
+	/* now give up all channels */
+	if (conn->lchan == lchan)
+		conn->lchan = NULL;
+	if (conn->ho_lchan == lchan) {
+		bsc_clear_handover(conn, 0);
+		conn->ho_lchan = NULL;
+	}
+	lchan->conn = NULL;
+
+	gsm0808_clear(conn);
+
+	if (destruct)
+		subscr_con_free(conn);
+}
+
+static void handle_chan_ack(struct gsm_subscriber_connection *conn,
+			    struct bsc_api *api, struct gsm_lchan *lchan)
+{
+	if (conn->secondary_lchan != lchan)
+		return;
+
+	LOGP(DMSC, LOGL_NOTICE, "Sending assignment on chan: %p\n", lchan);
+	gsm48_send_rr_ass_cmd(conn->lchan, lchan, 0x3);
+}
+
+static void handle_chan_nack(struct gsm_subscriber_connection *conn,
+			     struct bsc_api *api, struct gsm_lchan *lchan)
+{
+	if (conn->secondary_lchan != lchan)
+		return;
+
+	LOGP(DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n");
+	conn->secondary_lchan->conn = NULL;
+	conn->secondary_lchan = NULL;
+}
+
+static __attribute__((constructor)) void on_dso_load_bsc(void)
+{
+	register_signal_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL);
+}
diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c
new file mode 100644
index 0000000..0072bb6
--- /dev/null
+++ b/src/libbsc/bsc_init.c
@@ -0,0 +1,466 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+#include <openbsc/misdn.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <openbsc/system_information.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/chan_alloc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/ipaccess.h>
+
+/* global pointer to the gsm network data structure */
+extern struct gsm_network *bsc_gsmnet;
+
+static void patch_nm_tables(struct gsm_bts *bts);
+
+/* Callback function for NACK on the OML NM */
+static int oml_msg_nack(struct nm_nack_signal_data *nack)
+{
+	int i;
+
+	if (nack->mt == NM_MT_SET_BTS_ATTR_NACK) {
+
+		LOGP(DNM, LOGL_FATAL, "Failed to set BTS attributes. That is fatal. "
+				"Was the bts type and frequency properly specified?\n");
+		exit(-1);
+	} else {
+		LOGP(DNM, LOGL_ERROR, "Got a NACK going to drop the OML links.\n");
+		for (i = 0; i < bsc_gsmnet->num_bts; ++i) {
+			struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, i);
+			if (is_ipaccess_bts(bts))
+				ipaccess_drop_oml(bts);
+		}
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	struct nm_nack_signal_data *nack;
+
+	switch (signal) {
+	case S_NM_NACK:
+		nack = signal_data;
+		return oml_msg_nack(nack);
+	default:
+		break;
+	}
+	return 0;
+}
+
+int bsc_shutdown_net(struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr);
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts);
+	}
+
+	return 0;
+}
+
+static int generate_and_rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i)
+{
+	struct gsm_bts *bts = trx->bts;
+	int si_len, rc, j;
+
+	/* Only generate SI if this SI is not in "static" (user-defined) mode */
+	if (!(bts->si_mode_static & (1 << i))) {
+		rc = gsm_generate_si(bts, i);
+		if (rc < 0)
+			return rc;
+		si_len = rc;
+	}
+
+	DEBUGP(DRR, "SI%s: %s\n", gsm_sitype_name(i),
+		hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN));
+
+	switch (i) {
+	case SYSINFO_TYPE_5:
+	case SYSINFO_TYPE_5bis:
+	case SYSINFO_TYPE_5ter:
+	case SYSINFO_TYPE_6:
+		if (trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) {
+			/* HSL has mistaken SACCH INFO MODIFY for SACCH FILLING,
+			 * so we need a special workaround here */
+			/* This assumes a combined BCCH and TCH on TS1...7 */
+			for (j = 0; j < 4; j++)
+				rsl_sacch_info_modify(&trx->ts[0].lchan[j],
+						      gsm_sitype2rsl(i),
+						      GSM_BTS_SI(bts, i), si_len);
+			for (j = 1; j < 8; j++) {
+				rsl_sacch_info_modify(&trx->ts[j].lchan[0],
+						      gsm_sitype2rsl(i),
+						      GSM_BTS_SI(bts, i), si_len);
+				rsl_sacch_info_modify(&trx->ts[j].lchan[1],
+						      gsm_sitype2rsl(i),
+						      GSM_BTS_SI(bts, i), si_len);
+			}
+		} else
+			rc = rsl_sacch_filling(trx, gsm_sitype2rsl(i),
+					       GSM_BTS_SI(bts, i), rc);
+		break;
+	default:
+		rc = rsl_bcch_info(trx, gsm_sitype2rsl(i),
+				   GSM_BTS_SI(bts, i), rc);
+		break;
+	}
+
+	return rc;
+}
+
+/* set all system information types */
+static int set_system_infos(struct gsm_bts_trx *trx)
+{
+	int i, rc;
+	struct gsm_bts *bts = trx->bts;
+
+	bts->si_common.cell_sel_par.ms_txpwr_max_ccch =
+			ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+	bts->si_common.cell_sel_par.neci = bts->network->neci;
+
+	/* First, we determine which of the SI messages we actually need */
+
+	if (trx == bts->c0) {
+		/* 1...4 are always present on a C0 TRX */
+		for (i = SYSINFO_TYPE_1; i <= SYSINFO_TYPE_4; i++)
+			bts->si_valid |= (1 << i);
+
+		/* 13 is always present on a C0 TRX of a GPRS BTS */
+		if (bts->gprs.mode != BTS_GPRS_NONE)
+			bts->si_valid |= (1 << SYSINFO_TYPE_13);
+	}
+
+	/* 5 and 6 are always present on every TRX */
+	bts->si_valid |= (1 << SYSINFO_TYPE_5);
+	bts->si_valid |= (1 << SYSINFO_TYPE_6);
+
+	/* Second, we generate and send the selected SI via RSL */
+	for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) {
+		if (!(bts->si_valid & (1 << i)))
+			continue;
+
+		rc = generate_and_rsl_si(trx, i);
+		if (rc < 0)
+			goto err_out;
+	}
+
+	return 0;
+err_out:
+	LOGP(DRR, LOGL_ERROR, "Cannot generate SI %u for BTS %u, most likely "
+		"a problem with neighbor cell list generation\n",
+		i, bts->nr);
+	return rc;
+}
+
+/* Produce a MA as specified in 10.5.2.21 */
+static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
+{
+	/* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs
+	 * and the MA */
+	struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc;
+	struct bitvec *ts_arfcn = &ts->hopping.arfcns;
+	struct bitvec *ma = &ts->hopping.ma;
+	unsigned int num_cell_arfcns, bitnum, n_chan;
+	int i;
+
+	/* re-set the MA to all-zero */
+	ma->cur_bit = 0;
+	ts->hopping.ma_len = 0;
+	memset(ma->data, 0, ma->data_len);
+
+	if (!ts->hopping.enabled)
+		return 0;
+
+	/* count the number of ARFCNs in the cell channel allocation */
+	num_cell_arfcns = 0;
+	for (i = 1; i < 1024; i++) {
+		if (bitvec_get_bit_pos(cell_chan, i))
+			num_cell_arfcns++;
+	}
+
+	/* pad it to octet-aligned number of bits */
+	ts->hopping.ma_len = num_cell_arfcns / 8;
+	if (num_cell_arfcns % 8)
+		ts->hopping.ma_len++;
+
+	n_chan = 0;
+	for (i = 1; i < 1024; i++) {
+		if (!bitvec_get_bit_pos(cell_chan, i))
+			continue;
+		/* set the corresponding bit in the MA */
+		bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+		if (bitvec_get_bit_pos(ts_arfcn, i))
+			bitvec_set_bit_pos(ma, bitnum, 1);
+		else
+			bitvec_set_bit_pos(ma, bitnum, 0);
+		n_chan++;
+	}
+
+	/* ARFCN 0 is special: It is coded last in the bitmask */
+	if (bitvec_get_bit_pos(cell_chan, 0)) {
+		n_chan++;
+		/* set the corresponding bit in the MA */
+		bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+		if (bitvec_get_bit_pos(ts_arfcn, 0))
+			bitvec_set_bit_pos(ma, bitnum, 1);
+		else
+			bitvec_set_bit_pos(ma, bitnum, 0);
+	}
+
+	return 0;
+}
+
+static void bootstrap_rsl(struct gsm_bts_trx *trx)
+{
+	unsigned int i;
+
+	LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) "
+		"on ARFCN %u using MCC=%u MNC=%u LAC=%u CID=%u BSIC=%u TSC=%u\n",
+		trx->bts->nr, trx->nr, trx->arfcn, bsc_gsmnet->country_code,
+		bsc_gsmnet->network_code, trx->bts->location_area_code,
+		trx->bts->cell_identity, trx->bts->bsic, trx->bts->tsc);
+	set_system_infos(trx);
+
+	for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+		generate_ma_for_ts(&trx->ts[i]);
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+	struct gsm_bts_trx *trx = isd->trx;
+	int ts_no, lchan_no;
+
+	if (subsys != SS_INPUT)
+		return -EINVAL;
+
+	switch (signal) {
+	case S_INP_TEI_UP:
+		if (isd->link_type == E1INP_SIGN_RSL)
+			bootstrap_rsl(trx);
+		break;
+	case S_INP_TEI_DN:
+		LOGP(DMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx);
+
+		if (isd->link_type == E1INP_SIGN_OML)
+			counter_inc(trx->bts->network->stats.bts.oml_fail);
+		else if (isd->link_type == E1INP_SIGN_RSL)
+			counter_inc(trx->bts->network->stats.bts.rsl_fail);
+
+		/*
+		 * free all allocated channels. change the nm_state so the
+		 * trx and trx_ts becomes unusable and chan_alloc.c can not
+		 * allocate from it.
+		 */
+		for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[ts_no];
+
+			for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) {
+				if (ts->lchan[lchan_no].state != LCHAN_S_NONE)
+					lchan_free(&ts->lchan[lchan_no]);
+				lchan_reset(&ts->lchan[lchan_no]);
+			}
+
+			ts->nm_state.operational = 0;
+			ts->nm_state.availability = 0;
+		}
+
+		trx->nm_state.operational = 0;
+		trx->nm_state.availability = 0;
+		trx->bb_transc.nm_state.operational = 0;
+		trx->bb_transc.nm_state.availability = 0;
+
+		abis_nm_clear_queue(trx->bts);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int bootstrap_bts(struct gsm_bts *bts)
+{
+	int i, n;
+
+	/* FIXME: What about secondary TRX of a BTS?  What about a BTS that has TRX
+	 * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */
+	switch (bts->band) {
+	case GSM_BAND_1800:
+		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
+			LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_1900:
+		if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) {
+			LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_900:
+		if (bts->c0->arfcn < 1 ||
+		   (bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
+		    bts->c0->arfcn > 1023)  {
+			LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 1-124, 955-1023.\n");
+			return -EINVAL;
+		}
+		break;
+	case GSM_BAND_850:
+		if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) {
+			LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n");
+			return -EINVAL;
+		}
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n");
+		return -EINVAL;
+	}
+
+	if (bts->network->auth_policy == GSM_AUTH_POLICY_ACCEPT_ALL &&
+	    !bts->si_common.rach_control.cell_bar)
+		LOGP(DNM, LOGL_ERROR, "\nWARNING: You are running an 'accept-all' "
+			"network on a BTS that is not barred.  This "
+			"configuration is likely to interfere with production "
+			"GSM networks and should only be used in a RF "
+			"shielded environment such as a faraday cage!\n\n");
+
+	/* Control Channel Description */
+	bts->si_common.chan_desc.att = 1;
+	bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5;
+	bts->si_common.chan_desc.bs_ag_blks_res = 1;
+
+	/* T3212 is set from vty/config */
+
+	/* Set ccch config by looking at ts config */
+	for (n=0, i=0; i<8; i++)
+		n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0;
+
+	switch (n) {
+	case 0:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
+		break;
+	case 1:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC;
+		break;
+	case 2:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC;
+		break;
+	case 3:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC;
+		break;
+	case 4:
+		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC;
+		break;
+	default:
+		LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n");
+		return -EINVAL;
+	}
+
+	/* some defaults for our system information */
+	bts->si_common.cell_options.radio_link_timeout = 7; /* 12 */
+
+	/* allow/disallow DTXu */
+	if (bts->network->dtx_enabled)
+		bts->si_common.cell_options.dtx = 0;
+	else
+		bts->si_common.cell_options.dtx = 2;
+
+	bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
+
+	bts->si_common.cell_sel_par.acs = 0;
+
+	bts->si_common.ncc_permitted = 0xff;
+
+	paging_init(bts);
+
+	return 0;
+}
+
+int bsc_bootstrap_network(int (*mncc_recv)(struct gsm_network *, struct msgb *),
+			  const char *config_file)
+{
+	struct telnet_connection dummy_conn;
+	struct gsm_bts *bts;
+	int rc;
+
+	/* initialize our data structures */
+	bsc_gsmnet = gsm_network_init(1, 1, mncc_recv);
+	if (!bsc_gsmnet)
+		return -ENOMEM;
+
+	bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC");
+	bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC");
+
+	/* our vty command code expects vty->priv to point to a telnet_connection */
+	dummy_conn.priv = bsc_gsmnet;
+	rc = vty_read_config_file(config_file, &dummy_conn);
+	if (rc < 0) {
+		LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	rc = telnet_init(tall_bsc_ctx, bsc_gsmnet, 4242);
+	if (rc < 0)
+		return rc;
+
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+
+	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+		rc = bootstrap_bts(bts);
+
+		switch (bts->type) {
+		case GSM_BTS_TYPE_NANOBTS:
+		case GSM_BTS_TYPE_HSL_FEMTO:
+			break;
+		default:
+			rc = e1_reconfig_bts(bts);
+			break;
+		}
+
+		if (rc < 0) {
+			fprintf(stderr, "Error in E1 input driver setup\n");
+			exit (1);
+		}
+	}
+
+	/* initialize nanoBTS support omce */
+	rc = ipaccess_setup(bsc_gsmnet);
+	rc = hsl_setup(bsc_gsmnet);
+
+	return 0;
+}
diff --git a/src/libbsc/bsc_msc.c b/src/libbsc/bsc_msc.c
new file mode 100644
index 0000000..508697a
--- /dev/null
+++ b/src/libbsc/bsc_msc.c
@@ -0,0 +1,259 @@
+/* Routines to talk to the MSC using the IPA Protocol */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_msc.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+
+#include <osmocore/write_queue.h>
+#include <osmocore/talloc.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static void connection_loss(struct bsc_msc_connection *con)
+{
+	struct bsc_fd *fd;
+
+	fd = &con->write_queue.bfd;
+
+	close(fd->fd);
+	fd->fd = -1;
+	fd->cb = write_queue_bfd_cb;
+	fd->when = 0;
+
+	con->is_connected = 0;
+	con->first_contact = 0;
+	con->connection_loss(con);
+}
+
+static void msc_con_timeout(void *_con)
+{
+	struct bsc_msc_connection *con = _con;
+
+	LOGP(DMSC, LOGL_ERROR, "MSC Connection timeout.\n");
+	bsc_msc_lost(con);
+}
+
+/* called in the case of a non blocking connect */
+static int msc_connection_connect(struct bsc_fd *fd, unsigned int what)
+{
+	int rc;
+	int val;
+	struct bsc_msc_connection *con;
+	struct write_queue *queue;
+
+	socklen_t len = sizeof(val);
+
+	if ((what & BSC_FD_WRITE) == 0) {
+		LOGP(DMSC, LOGL_ERROR, "Callback but not writable.\n");
+		return -1;
+	}
+
+	queue = container_of(fd, struct write_queue, bfd);
+	con = container_of(queue, struct bsc_msc_connection, write_queue);
+
+	/* From here on we will either be connected or reconnect */
+	bsc_del_timer(&con->timeout_timer);
+
+	/* check the socket state */
+	rc = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &val, &len);
+	if (rc != 0) {
+		LOGP(DMSC, LOGL_ERROR, "getsockopt for the MSC socket failed.\n");
+		goto error;
+	}
+	if (val != 0) {
+		LOGP(DMSC, LOGL_ERROR, "Not connected to the MSC: %d\n", val);
+		goto error;
+	}
+
+
+	/* go to full operation */
+	fd->cb = write_queue_bfd_cb;
+	fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+
+	con->is_connected = 1;
+	LOGP(DMSC, LOGL_NOTICE, "(Re)Connected to the MSC.\n");
+	if (con->connected)
+		con->connected(con);
+	return 0;
+
+error:
+	bsc_unregister_fd(fd);
+	connection_loss(con);
+	return -1;
+}
+static void setnonblocking(struct bsc_fd *fd)
+{
+	int flags;
+
+	flags = fcntl(fd->fd, F_GETFL);
+	if (flags < 0) {
+		perror("fcntl get failed");
+		close(fd->fd);
+		fd->fd = -1;
+		return;
+	}
+
+	flags |= O_NONBLOCK;
+	flags = fcntl(fd->fd, F_SETFL, flags);
+	if (flags < 0) {
+		perror("fcntl get failed");
+		close(fd->fd);
+		fd->fd = -1;
+		return;
+	}
+}
+
+int bsc_msc_connect(struct bsc_msc_connection *con)
+{
+	struct bsc_fd *fd;
+	struct sockaddr_in sin;
+	int on = 1, ret;
+
+	LOGP(DMSC, LOGL_NOTICE, "Attempting to connect MSC at %s:%d\n", con->ip, con->port);
+
+	con->is_connected = 0;
+
+	fd = &con->write_queue.bfd;
+	fd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	fd->priv_nr = 1;
+
+	if (fd->fd < 0) {
+		perror("Creating TCP socket failed");
+		return fd->fd;
+	}
+
+	/* make it non blocking */
+	setnonblocking(fd);
+
+	/* set the socket priority */
+	ret = setsockopt(fd->fd, IPPROTO_IP, IP_TOS,
+			 &con->prio, sizeof(con->prio));
+	if (ret != 0)
+		LOGP(DMSC, LOGL_ERROR, "Failed to set prio to %d. %s\n",
+		     con->prio, strerror(errno));
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(con->port);
+	inet_aton(con->ip, &sin.sin_addr);
+
+	setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	ret = connect(fd->fd, (struct sockaddr *) &sin, sizeof(sin));
+
+	if (ret == -1 && errno == EINPROGRESS) {
+		LOGP(DMSC, LOGL_ERROR, "MSC Connection in progress\n");
+		fd->when = BSC_FD_WRITE;
+		fd->cb = msc_connection_connect;
+		con->timeout_timer.cb = msc_con_timeout;
+		con->timeout_timer.data = con;
+		bsc_schedule_timer(&con->timeout_timer, 20, 0);
+	} else if (ret < 0) {
+		perror("Connection failed");
+		connection_loss(con);
+		return ret;
+	} else {
+		fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+		fd->cb = write_queue_bfd_cb;
+		con->is_connected = 1;
+		if (con->connected)
+			con->connected(con);
+	}
+
+	ret = bsc_register_fd(fd);
+	if (ret < 0) {
+		perror("Registering the fd failed");
+		close(fd->fd);
+		return ret;
+	}
+
+	return ret;
+}
+
+struct bsc_msc_connection *bsc_msc_create(const char *ip, int port, int prio)
+{
+	struct bsc_msc_connection *con;
+
+	con = talloc_zero(NULL, struct bsc_msc_connection);
+	if (!con) {
+		LOGP(DMSC, LOGL_FATAL, "Failed to create the MSC connection.\n");
+		return NULL;
+	}
+
+	con->ip = ip;
+	con->port = port;
+	con->prio = prio;
+	write_queue_init(&con->write_queue, 100);
+	return con;
+}
+
+void bsc_msc_lost(struct bsc_msc_connection *con)
+{
+	write_queue_clear(&con->write_queue);
+	bsc_del_timer(&con->timeout_timer);
+
+	if (con->write_queue.bfd.fd >= 0)
+		bsc_unregister_fd(&con->write_queue.bfd);
+	connection_loss(con);
+}
+
+static void reconnect_msc(void *_msc)
+{
+	struct bsc_msc_connection *con = _msc;
+
+	LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n");
+	bsc_msc_connect(con);
+}
+
+void bsc_msc_schedule_connect(struct bsc_msc_connection *con)
+{
+	LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n");
+	con->reconnect_timer.cb = reconnect_msc;
+	con->reconnect_timer.data = con;
+	bsc_schedule_timer(&con->reconnect_timer, 5, 0);
+}
+
+struct msgb *bsc_msc_id_get_resp(const char *token)
+{
+	struct msgb *msg;
+
+	if (!token) {
+		LOGP(DMSC, LOGL_ERROR, "No token specified.\n");
+		return NULL;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "id resp");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create the message.\n");
+		return NULL;
+	}
+
+	msg->l2h = msgb_v_put(msg, IPAC_MSGT_ID_RESP);
+	msgb_l16tv_put(msg, strlen(token) + 1,
+			IPAC_IDTAG_UNITNAME, (u_int8_t *) token);
+	return msg;
+}
diff --git a/src/libbsc/bsc_rll.c b/src/libbsc/bsc_rll.c
new file mode 100644
index 0000000..722f3fa
--- /dev/null
+++ b/src/libbsc/bsc_rll.c
@@ -0,0 +1,141 @@
+/* GSM BSC Radio Link Layer API
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/linuxlist.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+
+struct bsc_rll_req {
+	struct llist_head list;
+	struct timer_list timer;
+
+	struct gsm_lchan *lchan;
+	u_int8_t link_id;
+
+	void (*cb)(struct gsm_lchan *lchan, u_int8_t link_id,
+		   void *data, enum bsc_rllr_ind);
+	void *data;
+};
+
+/* we only compare C1, C2 and SAPI */
+#define LINKID_MASK	0xC7
+
+static LLIST_HEAD(bsc_rll_reqs);
+
+static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type)
+{
+	llist_del(&rllr->list);
+	rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type);
+	talloc_free(rllr);
+}
+
+static void timer_cb(void *_rllr)
+{
+	struct bsc_rll_req *rllr = _rllr;
+
+	complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT);
+}
+
+/* establish a RLL connection with given SAPI / priority */
+int rll_establish(struct gsm_lchan *lchan, u_int8_t sapi,
+		  void (*cb)(struct gsm_lchan *, u_int8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data)
+{
+	struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req);
+	u_int8_t link_id;
+	if (!rllr)
+		return -ENOMEM;
+
+	link_id = sapi;
+
+	/* If we are a TCH and not in signalling mode, we need to
+	 * indicate that the new RLL connection is to be made on the SACCH */
+	if ((lchan->type == GSM_LCHAN_TCH_F ||
+	     lchan->type == GSM_LCHAN_TCH_H) && sapi != 0)
+		link_id |= 0x40;
+
+	rllr->lchan = lchan;
+	rllr->link_id = link_id;
+	rllr->cb = cb;
+	rllr->data = data;
+
+	llist_add(&rllr->list, &bsc_rll_reqs);
+
+	rllr->timer.cb = &timer_cb;
+	rllr->timer.data = rllr;
+
+	bsc_schedule_timer(&rllr->timer, 10, 0);
+
+	/* send the RSL RLL ESTablish REQuest */
+	return rsl_establish_request(rllr->lchan, rllr->link_id);
+}
+
+/* Called from RSL code in case we have received an indication regarding
+ * any RLL link */
+void rll_indication(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t type)
+{
+	struct bsc_rll_req *rllr, *rllr2;
+
+	llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+		if (rllr->lchan == lchan &&
+		    (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) {
+			bsc_del_timer(&rllr->timer);
+			complete_rllr(rllr, type);
+			return;
+		}
+	}
+}
+
+static int rll_lchan_signal(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct challoc_signal_data *challoc;
+	struct bsc_rll_req *rllr, *rllr2;
+
+	if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED)
+		return 0;
+
+	challoc = (struct challoc_signal_data *) signal_data;
+
+	llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+		if (rllr->lchan == challoc->lchan) {
+			bsc_del_timer(&rllr->timer);
+			complete_rllr(rllr, BSC_RLLR_IND_ERR_IND);
+		}
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_rll(void)
+{
+	register_signal_handler(SS_CHALLOC, rll_lchan_signal, NULL);
+}
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
new file mode 100644
index 0000000..c0909db
--- /dev/null
+++ b/src/libbsc/bsc_vty.c
@@ -0,0 +1,2799 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_om2000.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/vty.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/system_information.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/osmo_bsc_rf.h>
+
+#include "../../bscconfig.h"
+
+/* FIXME: this should go to some common file */
+static const struct value_string gprs_ns_timer_strs[] = {
+	{ 0, "tns-block" },
+	{ 1, "tns-block-retries" },
+	{ 2, "tns-reset" },
+	{ 3, "tns-reset-retries" },
+	{ 4, "tns-test" },
+	{ 5, "tns-alive" },
+	{ 6, "tns-alive-retries" },
+	{ 0, NULL }
+};
+
+static const struct value_string gprs_bssgp_cfg_strs[] = {
+	{ 0,	"blocking-timer" },
+	{ 1,	"blocking-retries" },
+	{ 2,	"unblocking-retries" },
+	{ 3,	"reset-timer" },
+	{ 4,	"reset-retries" },
+	{ 5,	"suspend-timer" },
+	{ 6,	"suspend-retries" },
+	{ 7,	"resume-timer" },
+	{ 8,	"resume-retries" },
+	{ 9,	"capability-update-timer" },
+	{ 10,	"capability-update-retries" },
+	{ 0,	NULL }
+};
+
+static const struct value_string bts_neigh_mode_strs[] = {
+	{ NL_MODE_AUTOMATIC, "automatic" },
+	{ NL_MODE_MANUAL, "manual" },
+	{ NL_MODE_MANUAL_SI5SEP, "manual-si5" },
+	{ 0, NULL }
+};
+
+struct cmd_node net_node = {
+	GSMNET_NODE,
+	"%s(network)#",
+	1,
+};
+
+struct cmd_node bts_node = {
+	BTS_NODE,
+	"%s(bts)#",
+	1,
+};
+
+struct cmd_node trx_node = {
+	TRX_NODE,
+	"%s(trx)#",
+	1,
+};
+
+struct cmd_node ts_node = {
+	TS_NODE,
+	"%s(ts)#",
+	1,
+};
+
+extern struct gsm_network *bsc_gsmnet;
+
+struct gsm_network *gsmnet_from_vty(struct vty *v)
+{
+	/* In case we read from the config file, the vty->priv cannot
+	 * point to a struct telnet_connection, and thus conn->priv
+	 * will not point to the gsm_network structure */
+#if 0
+	struct telnet_connection *conn = v->priv;
+	return (struct gsm_network *) conn->priv;
+#else
+	return bsc_gsmnet;
+#endif
+}
+
+static int dummy_config_write(struct vty *v)
+{
+	return CMD_SUCCESS;
+}
+
+static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+{
+	vty_out(vty,"Oper '%s', Admin %u, Avail '%s'%s",
+		nm_opstate_name(nms->operational), nms->administrative,
+		nm_avail_name(nms->availability), VTY_NEWLINE);
+}
+
+static void dump_pchan_load_vty(struct vty *vty, char *prefix,
+				const struct pchan_load *pl)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) {
+		const struct load_counter *lc = &pl->pchan[i];
+		unsigned int percent;
+
+		if (lc->total == 0)
+			continue;
+
+		percent = (lc->used * 100) / lc->total;
+
+		vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix,
+			gsm_pchan_name(i), percent, lc->used, lc->total,
+			VTY_NEWLINE);
+	}
+}
+
+static void net_dump_vty(struct vty *vty, struct gsm_network *net)
+{
+	struct pchan_load pl;
+
+	vty_out(vty, "BSC is on Country Code %u, Network Code %u "
+		"and has %u BTS%s", net->country_code, net->network_code,
+		net->num_bts, VTY_NEWLINE);
+	vty_out(vty, "  Long network name: '%s'%s",
+		net->name_long, VTY_NEWLINE);
+	vty_out(vty, "  Short network name: '%s'%s",
+		net->name_short, VTY_NEWLINE);
+	vty_out(vty, "  Authentication policy: %s%s",
+		gsm_auth_policy_name(net->auth_policy), VTY_NEWLINE);
+	vty_out(vty, "  Location updating reject cause: %u%s",
+		net->reject_cause, VTY_NEWLINE);
+	vty_out(vty, "  Encryption: A5/%u%s", net->a5_encryption,
+		VTY_NEWLINE);
+	vty_out(vty, "  NECI (TCH/H): %u%s", net->neci,
+		VTY_NEWLINE);
+	vty_out(vty, "  Use TCH for Paging any: %d%s", net->pag_any_tch,
+		VTY_NEWLINE);
+	vty_out(vty, "  RRLP Mode: %s%s", rrlp_mode_name(net->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, "  MM Info: %s%s", net->send_mm_info ? "On" : "Off",
+		VTY_NEWLINE);
+	vty_out(vty, "  Handover: %s%s", net->handover.active ? "On" : "Off",
+		VTY_NEWLINE);
+	network_chan_load(&pl, net);
+	vty_out(vty, "  Current Channel Load:%s", VTY_NEWLINE);
+	dump_pchan_load_vty(vty, "    ", &pl);
+
+	/* show rf */
+	if (net->msc_data)
+		vty_out(vty, "  Last RF Command: %s%s",
+			net->msc_data->rf_ctl->last_state_command,
+			VTY_NEWLINE);
+}
+
+DEFUN(show_net, show_net_cmd, "show network",
+	SHOW_STR "Display information about a GSM NETWORK\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	net_dump_vty(vty, net);
+
+	return CMD_SUCCESS;
+}
+
+static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l)
+{
+	struct e1inp_line *line;
+
+	if (!e1l) {
+		vty_out(vty, "   None%s", VTY_NEWLINE);
+		return;
+	}
+
+	line = e1l->ts->line;
+
+	vty_out(vty, "    E1 Line %u, Type %s: Timeslot %u, Mode %s%s",
+		line->num, line->driver->name, e1l->ts->num,
+		e1inp_signtype_name(e1l->type), VTY_NEWLINE);
+	vty_out(vty, "    E1 TEI %u, SAPI %u%s",
+		e1l->tei, e1l->sapi, VTY_NEWLINE);
+}
+
+static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct pchan_load pl;
+
+	vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+		"BSIC %u, TSC %u and %u TRX%s",
+		bts->nr, btstype2str(bts->type), gsm_band_name(bts->band),
+		bts->cell_identity,
+		bts->location_area_code, bts->bsic, bts->tsc,
+		bts->num_trx, VTY_NEWLINE);
+	vty_out(vty, "Description: %s%s",
+		bts->description ? bts->description : "(null)", VTY_NEWLINE);
+	vty_out(vty, "MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "Minimum Rx Level for Access: %i dBm%s",
+		rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min),
+		VTY_NEWLINE);
+	vty_out(vty, "Cell Reselection Hysteresis: %u dBm%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer,
+		VTY_NEWLINE);
+	vty_out(vty, "RACH Max transmissions: %u%s",
+		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+		VTY_NEWLINE);
+	if (bts->si_common.rach_control.cell_bar)
+		vty_out(vty, "  CELL IS BARRED%s", VTY_NEWLINE);
+	vty_out(vty, "System Information present: 0x%08x, static: 0x%08x%s",
+		bts->si_valid, bts->si_mode_static, VTY_NEWLINE);
+	if (is_ipaccess_bts(bts))
+		vty_out(vty, "  Unit ID: %u/%u/0, OML Stream ID 0x%02x%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id,
+			bts->oml_tei, VTY_NEWLINE);
+	else if (bts->type == GSM_BTS_TYPE_HSL_FEMTO)
+		vty_out(vty, "  Serial Number: %lu%s", bts->hsl.serno, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &bts->nm_state);
+	vty_out(vty, "  Site Mgr NM State: ");
+	net_dump_nmstate(vty, &bts->site_mgr.nm_state);
+	vty_out(vty, "  Paging: FIXME pending requests, %u free slots%s",
+		bts->paging.available_slots, VTY_NEWLINE);
+	if (is_ipaccess_bts(bts)) {
+		vty_out(vty, "  OML Link state: %s.%s",
+			bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE);
+	} else {
+		vty_out(vty, "  E1 Signalling Link:%s", VTY_NEWLINE);
+		e1isl_dump_vty(vty, bts->oml_link);
+	}
+
+	/* FIXME: chan_desc */
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+	vty_out(vty, "  Current Channel Load:%s", VTY_NEWLINE);
+	dump_pchan_load_vty(vty, "    ", &pl);
+}
+
+DEFUN(show_bts, show_bts_cmd, "show bts [number]",
+	SHOW_STR "Display information about a BTS\n"
+		"BTS number")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	int bts_nr;
+
+	if (argc != 0) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+		return CMD_SUCCESS;
+	}
+	/* print all BTS's */
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+		bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+
+	return CMD_SUCCESS;
+}
+
+/* utility functions */
+static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
+			  const char *ts, const char *ss)
+{
+	e1_link->e1_nr = atoi(line);
+	e1_link->e1_ts = atoi(ts);
+	if (!strcmp(ss, "full"))
+		e1_link->e1_ts_ss = 255;
+	else
+		e1_link->e1_ts_ss = atoi(ss);
+}
+
+static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link,
+				 const char *prefix)
+{
+	if (!e1_link->e1_ts)
+		return;
+
+	if (e1_link->e1_ts_ss == 255)
+		vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s",
+			prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE);
+	else
+		vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s",
+			prefix, e1_link->e1_nr, e1_link->e1_ts,
+			e1_link->e1_ts_ss, VTY_NEWLINE);
+}
+
+
+static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	vty_out(vty, "    timeslot %u%s", ts->nr, VTY_NEWLINE);
+	if (ts->pchan != GSM_PCHAN_NONE)
+		vty_out(vty, "     phys_chan_config %s%s",
+			gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+	vty_out(vty, "     hopping enabled %u%s",
+		ts->hopping.enabled, VTY_NEWLINE);
+	if (ts->hopping.enabled) {
+		unsigned int i;
+		vty_out(vty, "     hopping sequence-number %u%s",
+			ts->hopping.hsn, VTY_NEWLINE);
+		vty_out(vty, "     hopping maio %u%s",
+			ts->hopping.maio, VTY_NEWLINE);
+		for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+			if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+				continue;
+			vty_out(vty, "     hopping arfcn add %u%s",
+				i, VTY_NEWLINE);
+		}
+	}
+	config_write_e1_link(vty, &ts->e1_link, "     ");
+
+	if (ts->trx->bts->model->config_write_ts)
+		ts->trx->bts->model->config_write_ts(vty, ts);
+}
+
+static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
+{
+	int i;
+
+	vty_out(vty, "  trx %u%s", trx->nr, VTY_NEWLINE);
+	if (trx->description)
+		vty_out(vty, "   description %s%s", trx->description,
+			VTY_NEWLINE);
+	vty_out(vty, "   rf_locked %u%s",
+		trx->nm_state.administrative == NM_STATE_LOCKED ? 1 : 0,
+		VTY_NEWLINE);
+	vty_out(vty, "   arfcn %u%s", trx->arfcn, VTY_NEWLINE);
+	vty_out(vty, "   nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
+	vty_out(vty, "   max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
+	config_write_e1_link(vty, &trx->rsl_e1_link, "   rsl ");
+	vty_out(vty, "   rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE);
+
+	if (trx->bts->model->config_write_trx)
+		trx->bts->model->config_write_trx(vty, trx);
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		config_write_ts_single(vty, &trx->ts[i]);
+}
+
+static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts)
+{
+	unsigned int i;
+	vty_out(vty, "  gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
+		VTY_NEWLINE);
+	if (bts->gprs.mode == BTS_GPRS_NONE)
+		return;
+
+	vty_out(vty, "  gprs routing area %u%s", bts->gprs.rac,
+		VTY_NEWLINE);
+	vty_out(vty, "  gprs cell bvci %u%s", bts->gprs.cell.bvci,
+		VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++)
+		vty_out(vty, "  gprs cell timer %s %u%s",
+			get_value_string(gprs_bssgp_cfg_strs, i),
+			bts->gprs.cell.timer[i], VTY_NEWLINE);
+	vty_out(vty, "  gprs nsei %u%s", bts->gprs.nse.nsei,
+		VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++)
+		vty_out(vty, "  gprs ns timer %s %u%s",
+			get_value_string(gprs_ns_timer_strs, i),
+			bts->gprs.nse.timer[i], VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+		struct gsm_bts_gprs_nsvc *nsvc =
+					&bts->gprs.nsvc[i];
+		struct in_addr ia;
+
+		ia.s_addr = htonl(nsvc->remote_ip);
+		vty_out(vty, "  gprs nsvc %u nsvci %u%s", i,
+			nsvc->nsvci, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u local udp port %u%s", i,
+			nsvc->local_port, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u remote udp port %u%s", i,
+			nsvc->remote_port, VTY_NEWLINE);
+		vty_out(vty, "  gprs nsvc %u remote ip %s%s", i,
+			inet_ntoa(ia), VTY_NEWLINE);
+	}
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	int i;
+
+	vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE);
+	vty_out(vty, "  type %s%s", btstype2str(bts->type), VTY_NEWLINE);
+	if (bts->description)
+		vty_out(vty, "  description %s%s", bts->description, VTY_NEWLINE);
+	vty_out(vty, "  band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
+	vty_out(vty, "  cell_identity %u%s", bts->cell_identity, VTY_NEWLINE);
+	vty_out(vty, "  location_area_code %u%s", bts->location_area_code,
+		VTY_NEWLINE);
+	vty_out(vty, "  training_sequence_code %u%s", bts->tsc, VTY_NEWLINE);
+	vty_out(vty, "  base_station_id_code %u%s", bts->bsic, VTY_NEWLINE);
+	vty_out(vty, "  ms max power %u%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "  cell reselection hysteresis %u%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "  rxlev access min %u%s",
+		bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE);
+
+	if (bts->si_common.cell_ro_sel_par.present) {
+		struct gsm48_si_selection_params *sp;
+		sp = &bts->si_common.cell_ro_sel_par;
+
+		if (sp->cbq)
+			vty_out(vty, "  cell bar qualify %u%s",
+				sp->cbq, VTY_NEWLINE);
+
+		if (sp->cell_resel_off)
+			vty_out(vty, "  cell reselection offset %u%s",
+				sp->cell_resel_off*2, VTY_NEWLINE);
+
+		if (sp->temp_offs == 7)
+			vty_out(vty, "  temporary offset infinite%s",
+				VTY_NEWLINE);
+		else if (sp->temp_offs)
+			vty_out(vty, "  temporary offset %u%s",
+				sp->temp_offs*10, VTY_NEWLINE);
+
+		if (sp->penalty_time == 31)
+			vty_out(vty, "  penalty time reserved%s",
+				VTY_NEWLINE);
+		else if (sp->penalty_time)
+			vty_out(vty, "  penalty time %u%s",
+				(sp->penalty_time*20)+20, VTY_NEWLINE);
+	}
+
+	if (bts->si_common.chan_desc.t3212)
+		vty_out(vty, "  periodic location update %u%s",
+			bts->si_common.chan_desc.t3212 * 6, VTY_NEWLINE);
+	vty_out(vty, "  channel allocator %s%s",
+		bts->chan_alloc_reverse ? "descending" : "ascending",
+		VTY_NEWLINE);
+	vty_out(vty, "  rach tx integer %u%s",
+		bts->si_common.rach_control.tx_integer, VTY_NEWLINE);
+	vty_out(vty, "  rach max transmission %u%s",
+		rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+		VTY_NEWLINE);
+
+	if (bts->rach_b_thresh != -1)
+		vty_out(vty, "  rach nm busy threshold %u%s",
+			bts->rach_b_thresh, VTY_NEWLINE);
+	if (bts->rach_ldavg_slots != -1)
+		vty_out(vty, "  rach nm load average %u%s",
+			bts->rach_ldavg_slots, VTY_NEWLINE);
+	if (bts->si_common.rach_control.cell_bar)
+		vty_out(vty, "  cell barred 1%s", VTY_NEWLINE);
+	if ((bts->si_common.rach_control.t2 & 0x4) == 0)
+		vty_out(vty, "  rach emergency call allowed 1%s", VTY_NEWLINE);
+	for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) {
+		if (bts->si_mode_static & (1 << i)) {
+			vty_out(vty, "  system-information %s mode static%s",
+				get_value_string(osmo_sitype_strs, i), VTY_NEWLINE);
+			vty_out(vty, "  system-information %s static %s%s",
+				get_value_string(osmo_sitype_strs, i),
+				hexdump_nospc(bts->si_buf[i], sizeof(bts->si_buf[i])),
+				VTY_NEWLINE);
+		}
+	}
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		vty_out(vty, "  ip.access unit_id %u %u%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+		vty_out(vty, "  oml ip.access stream_id %u%s", bts->oml_tei, VTY_NEWLINE);
+		break;
+	case GSM_BTS_TYPE_HSL_FEMTO:
+		vty_out(vty, "  hsl serial-number %lu%s", bts->hsl.serno, VTY_NEWLINE);
+		break;
+	default:
+		config_write_e1_link(vty, &bts->oml_e1_link, "  oml ");
+		vty_out(vty, "  oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
+		break;
+	}
+
+	/* if we have a limit, write it */
+	if (bts->paging.free_chans_need >= 0)
+		vty_out(vty, "  paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE);
+
+	vty_out(vty, "  neighbor-list mode %s%s",
+		get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE);
+	if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) {
+		for (i = 0; i < 1024; i++) {
+			if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i))
+				vty_out(vty, "  neighbor-list add arfcn %u%s",
+					i, VTY_NEWLINE);
+		}
+	}
+	if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
+		for (i = 0; i < 1024; i++) {
+			if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i))
+				vty_out(vty, "  si5 neighbor-list add arfcn %u%s",
+					i, VTY_NEWLINE);
+		}
+	}
+
+	config_write_bts_gprs(vty, bts);
+
+	if (bts->model->config_write_bts)
+		bts->model->config_write_bts(vty, bts);
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		config_write_trx_single(vty, trx);
+}
+
+static int config_write_bts(struct vty *v)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(v);
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &gsmnet->bts_list, list)
+		config_write_bts_single(v, bts);
+
+	return CMD_SUCCESS;
+}
+
+static int config_write_net(struct vty *vty)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	vty_out(vty, "network%s", VTY_NEWLINE);
+	vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE);
+	vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE);
+	vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE);
+	vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE);
+	vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE);
+	vty_out(vty, " location updating reject cause %u%s",
+		gsmnet->reject_cause, VTY_NEWLINE);
+	vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
+	vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE);
+	vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE);
+	vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode),
+		VTY_NEWLINE);
+	vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE);
+	vty_out(vty, " handover %u%s", gsmnet->handover.active, VTY_NEWLINE);
+	vty_out(vty, " handover window rxlev averaging %u%s",
+		gsmnet->handover.win_rxlev_avg, VTY_NEWLINE);
+	vty_out(vty, " handover window rxqual averaging %u%s",
+		gsmnet->handover.win_rxqual_avg, VTY_NEWLINE);
+	vty_out(vty, " handover window rxlev neighbor averaging %u%s",
+		gsmnet->handover.win_rxlev_avg_neigh, VTY_NEWLINE);
+	vty_out(vty, " handover power budget interval %u%s",
+		gsmnet->handover.pwr_interval, VTY_NEWLINE);
+	vty_out(vty, " handover power budget hysteresis %u%s",
+		gsmnet->handover.pwr_hysteresis, VTY_NEWLINE);
+	vty_out(vty, " handover maximum distance %u%s",
+		gsmnet->handover.max_distance, VTY_NEWLINE);
+	vty_out(vty, " timer t3101 %u%s", gsmnet->T3101, VTY_NEWLINE);
+	vty_out(vty, " timer t3103 %u%s", gsmnet->T3103, VTY_NEWLINE);
+	vty_out(vty, " timer t3105 %u%s", gsmnet->T3105, VTY_NEWLINE);
+	vty_out(vty, " timer t3107 %u%s", gsmnet->T3107, VTY_NEWLINE);
+	vty_out(vty, " timer t3109 %u%s", gsmnet->T3109, VTY_NEWLINE);
+	vty_out(vty, " timer t3111 %u%s", gsmnet->T3111, VTY_NEWLINE);
+	vty_out(vty, " timer t3113 %u%s", gsmnet->T3113, VTY_NEWLINE);
+	vty_out(vty, " timer t3115 %u%s", gsmnet->T3115, VTY_NEWLINE);
+	vty_out(vty, " timer t3117 %u%s", gsmnet->T3117, VTY_NEWLINE);
+	vty_out(vty, " timer t3119 %u%s", gsmnet->T3119, VTY_NEWLINE);
+	vty_out(vty, " timer t3122 %u%s", gsmnet->T3122, VTY_NEWLINE);
+	vty_out(vty, " timer t3141 %u%s", gsmnet->T3141, VTY_NEWLINE);
+	vty_out(vty, " dtx-used %u%s", gsmnet->dtx_enabled, VTY_NEWLINE);
+	vty_out(vty, " subscriber-keep-in-ram %d%s",
+		gsmnet->keep_subscr, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+{
+	vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+		trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+	vty_out(vty, "Description: %s%s",
+		trx->description ? trx->description : "(null)", VTY_NEWLINE);
+	vty_out(vty, "  RF Nominal Power: %d dBm, reduced by %u dB, "
+		"resulting BS power: %d dBm%s",
+		trx->nominal_power, trx->max_power_red,
+		trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &trx->nm_state);
+	vty_out(vty, "  Baseband Transceiver NM State: ");
+	net_dump_nmstate(vty, &trx->bb_transc.nm_state);
+	if (is_ipaccess_bts(trx->bts)) {
+		vty_out(vty, "  ip.access stream ID: 0x%02x%s",
+			trx->rsl_tei, VTY_NEWLINE);
+	} else {
+		vty_out(vty, "  E1 Signalling Link:%s", VTY_NEWLINE);
+		e1isl_dump_vty(vty, trx->rsl_link);
+	}
+}
+
+DEFUN(show_trx,
+      show_trx_cmd,
+      "show trx [bts_nr] [trx_nr]",
+	SHOW_STR "Display information about a TRX\n"
+	"BTS Number\n"
+	"TRX Number\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts = NULL;
+	struct gsm_bts_trx *trx;
+	int bts_nr, trx_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+		trx_dump_vty(vty, trx);
+		return CMD_SUCCESS;
+	}
+	if (bts) {
+		/* print all TRX in this BTS */
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			trx_dump_vty(vty, trx);
+		}
+		return CMD_SUCCESS;
+	}
+
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			trx_dump_vty(vty, trx);
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s",
+		ts->trx->bts->nr, ts->trx->nr, ts->nr,
+		gsm_pchan_name(ts->pchan));
+	if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
+		vty_out(vty, " (%s mode)",
+			ts->flags & TS_F_PDCH_MODE ? "PDCH" : "TCH/F");
+	vty_out(vty, "%s", VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &ts->nm_state);
+	if (!is_ipaccess_bts(ts->trx->bts))
+		vty_out(vty, "  E1 Line %u, Timeslot %u, Subslot %u%s",
+			ts->e1_link.e1_nr, ts->e1_link.e1_ts,
+			ts->e1_link.e1_ts_ss, VTY_NEWLINE);
+}
+
+DEFUN(show_ts,
+      show_ts_cmd,
+      "show timeslot [bts_nr] [trx_nr] [ts_nr]",
+	SHOW_STR "Display information about a TS\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts = NULL;
+	struct gsm_bts_trx *trx = NULL;
+	struct gsm_bts_trx_ts *ts = NULL;
+	int bts_nr, trx_nr, ts_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+	}
+	if (argc >= 3) {
+		ts_nr = atoi(argv[2]);
+		if (ts_nr >= TRX_NR_TS) {
+			vty_out(vty, "%% can't find TS '%s'%s", argv[2],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		/* Fully Specified: print and exit */
+		ts = &trx->ts[ts_nr];
+		ts_dump_vty(vty, ts);
+		return CMD_SUCCESS;
+	}
+
+	if (bts && trx) {
+		/* Iterate over all TS in this TRX */
+		for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+			ts = &trx->ts[ts_nr];
+			ts_dump_vty(vty, ts);
+		}
+	} else if (bts) {
+		/* Iterate over all TRX in this BTS, TS in each TRX */
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+				ts = &trx->ts[ts_nr];
+				ts_dump_vty(vty, ts);
+			}
+		}
+	} else {
+		/* Iterate over all BTS, TRX in each BTS, TS in each TRX */
+		for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+			bts = gsm_bts_num(net, bts_nr);
+			for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+				trx = gsm_bts_trx_num(bts, trx_nr);
+				for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+					ts = &trx->ts[ts_nr];
+					ts_dump_vty(vty, ts);
+				}
+			}
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (subscr->name)
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (subscr->extension)
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	if (subscr->imsi)
+		vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+static void meas_rep_dump_uni_vty(struct vty *vty,
+				  struct gsm_meas_rep_unidir *mru,
+				  const char *prefix,
+				  const char *dir)
+{
+	vty_out(vty, "%s  RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ",
+		prefix, dir, rxlev2dbm(mru->full.rx_lev),
+			dir, rxlev2dbm(mru->sub.rx_lev));
+	vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s",
+		dir, mru->full.rx_qual, dir, mru->sub.rx_qual,
+		VTY_NEWLINE);
+}
+
+static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr,
+			      const char *prefix)
+{
+	vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE);
+	vty_out(vty, "%s  Flags: %s%s%s%s%s", prefix,
+			mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "",
+			mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "",
+			mr->flags & MEAS_REP_F_FPC ? "FPC " : "",
+			mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ",
+			VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_TO)
+		vty_out(vty, "%s  MS Timing Offset: %u%s", prefix,
+			mr->ms_timing_offset, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_L1)
+		vty_out(vty, "%s  L1 MS Power: %u dBm, Timing Advance: %u%s",
+			prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_DL_VALID)
+		meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
+	meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+}
+
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+	int idx;
+
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
+	vty_out(vty, "  Connection: %u, State: %s%s",
+		lchan->conn ? 1: 0,
+		gsm_lchans_name(lchan->state), VTY_NEWLINE);
+	vty_out(vty, "  BS Power: %u dBm, MS Power: %u dBm%s",
+		lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
+		- lchan->bs_power*2,
+		ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+		VTY_NEWLINE);
+	if (lchan->conn && lchan->conn->subscr) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_vty(vty, lchan->conn->subscr);
+	} else
+		vty_out(vty, "  No Subscriber%s", VTY_NEWLINE);
+	if (is_ipaccess_bts(lchan->ts->trx->bts)) {
+		struct in_addr ia;
+		ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+		vty_out(vty, "  Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s",
+			inet_ntoa(ia), lchan->abis_ip.bound_port,
+			lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
+			VTY_NEWLINE);
+	}
+
+	/* we want to report the last measurement report */
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+			       lchan->meas_rep_idx, 1);
+	meas_rep_dump_vty(vty, &lchan->meas_rep[idx], "  ");
+}
+
+static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+	struct gsm_meas_rep *mr;
+	int idx;
+
+	/* we want to report the last measurement report */
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+			       lchan->meas_rep_idx, 1);
+	mr =  &lchan->meas_rep[idx];
+
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u, Type %s - "
+		"L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		lchan->nr, gsm_lchant_name(lchan->type), mr->ms_l1.pwr,
+		rxlev2dbm(mr->dl.full.rx_lev),
+		rxlev2dbm(mr->ul.full.rx_lev),
+		VTY_NEWLINE);
+}
+
+static int lchan_summary(struct vty *vty, int argc, const char **argv,
+			 void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lchan;
+	int bts_nr, trx_nr, ts_nr, lchan_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS %s%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+	}
+	if (argc >= 2) {
+		trx_nr = atoi(argv[1]);
+		if (trx_nr >= bts->num_trx) {
+			vty_out(vty, "%% can't find TRX %s%s", argv[1],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		trx = gsm_bts_trx_num(bts, trx_nr);
+	}
+	if (argc >= 3) {
+		ts_nr = atoi(argv[2]);
+		if (ts_nr >= TRX_NR_TS) {
+			vty_out(vty, "%% can't find TS %s%s", argv[2],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &trx->ts[ts_nr];
+	}
+	if (argc >= 4) {
+		lchan_nr = atoi(argv[3]);
+		if (lchan_nr >= TS_MAX_LCHAN) {
+			vty_out(vty, "%% can't find LCHAN %s%s", argv[3],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		lchan = &ts->lchan[lchan_nr];
+		dump_cb(vty, lchan);
+		return CMD_SUCCESS;
+	}
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+			trx = gsm_bts_trx_num(bts, trx_nr);
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+				ts = &trx->ts[ts_nr];
+				for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN;
+				     lchan_nr++) {
+					lchan = &ts->lchan[lchan_nr];
+					if (lchan->type == GSM_LCHAN_NONE)
+						continue;
+					dump_cb(vty, lchan);
+				}
+			}
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(show_lchan,
+      show_lchan_cmd,
+      "show lchan [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	"Logical Channel Number\n")
+
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+      show_lchan_summary_cmd,
+      "show lchan summary [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	"Logical Channel Number\n")
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
+static void e1drv_dump_vty(struct vty *vty, struct e1inp_driver *drv)
+{
+	vty_out(vty, "E1 Input Driver %s%s", drv->name, VTY_NEWLINE);
+}
+
+DEFUN(show_e1drv,
+      show_e1drv_cmd,
+      "show e1_driver",
+	SHOW_STR "Display information about available E1 drivers\n")
+{
+	struct e1inp_driver *drv;
+
+	llist_for_each_entry(drv, &e1inp_driver_list, list)
+		e1drv_dump_vty(vty, drv);
+
+	return CMD_SUCCESS;
+}
+
+static void e1line_dump_vty(struct vty *vty, struct e1inp_line *line)
+{
+	vty_out(vty, "E1 Line Number %u, Name %s, Driver %s%s",
+		line->num, line->name ? line->name : "",
+		line->driver->name, VTY_NEWLINE);
+}
+
+DEFUN(show_e1line,
+      show_e1line_cmd,
+      "show e1_line [line_nr]",
+	SHOW_STR "Display information about a E1 line\n"
+	"E1 Line Number\n")
+{
+	struct e1inp_line *line;
+
+	if (argc >= 1) {
+		int num = atoi(argv[0]);
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			if (line->num == num) {
+				e1line_dump_vty(vty, line);
+				return CMD_SUCCESS;
+			}
+		}
+		return CMD_WARNING;
+	}	
+	
+	llist_for_each_entry(line, &e1inp_line_list, list)
+		e1line_dump_vty(vty, line);
+
+	return CMD_SUCCESS;
+}
+
+static void e1ts_dump_vty(struct vty *vty, struct e1inp_ts *ts)
+{
+	if (ts->type == E1INP_TS_TYPE_NONE)
+		return;
+	vty_out(vty, "E1 Timeslot %2u of Line %u is Type %s%s",
+		ts->num, ts->line->num, e1inp_tstype_name(ts->type),
+		VTY_NEWLINE);
+}
+
+DEFUN(show_e1ts,
+      show_e1ts_cmd,
+      "show e1_timeslot [line_nr] [ts_nr]",
+	SHOW_STR "Display information about a E1 timeslot\n"
+	"E1 Line Number\n" "E1 Timeslot Number\n")
+{
+	struct e1inp_line *line = NULL;
+	struct e1inp_ts *ts;
+	int ts_nr;
+
+	if (argc == 0) {
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) {
+				ts = &line->ts[ts_nr];
+				e1ts_dump_vty(vty, ts);
+			}
+		}
+		return CMD_SUCCESS;
+	}
+	if (argc >= 1) {
+		int num = atoi(argv[0]);
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			if (line->num == num)
+				break;
+		}
+		if (!line || line->num != num) {
+			vty_out(vty, "E1 line %s is invalid%s",
+				argv[0], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}	
+	if (argc >= 2) {
+		ts_nr = atoi(argv[1]);
+		if (ts_nr > NUM_E1_TS) {
+			vty_out(vty, "E1 timeslot %s is invalid%s",
+				argv[1], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &line->ts[ts_nr];
+		e1ts_dump_vty(vty, ts);
+		return CMD_SUCCESS;
+	} else {
+		for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) {
+			ts = &line->ts[ts_nr];
+			e1ts_dump_vty(vty, ts);
+		}
+		return CMD_SUCCESS;
+	}
+	return CMD_SUCCESS;
+}
+
+static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag)
+{
+	vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE);
+	subscr_dump_vty(vty, pag->subscr);
+}
+
+static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_paging_request *pag;
+
+	llist_for_each_entry(pag, &bts->paging.pending_requests, entry)
+		paging_dump_vty(vty, pag);
+}
+
+DEFUN(show_paging,
+      show_paging_cmd,
+      "show paging [bts_nr]",
+	SHOW_STR "Display information about paging reuqests of a BTS\n"
+	"BTS Number\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_bts *bts;
+	int bts_nr;
+
+	if (argc >= 1) {
+		/* use the BTS number that the user has specified */
+		bts_nr = atoi(argv[0]);
+		if (bts_nr >= net->num_bts) {
+			vty_out(vty, "%% can't find BTS %s%s", argv[0],
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		bts = gsm_bts_num(net, bts_nr);
+		bts_paging_dump_vty(vty, bts);
+		
+		return CMD_SUCCESS;
+	}
+	for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+		bts = gsm_bts_num(net, bts_nr);
+		bts_paging_dump_vty(vty, bts);
+	}
+
+	return CMD_SUCCESS;
+}
+
+#define NETWORK_STR "Configure the GSM network\n"
+
+DEFUN(cfg_net,
+      cfg_net_cmd,
+      "network", NETWORK_STR)
+{
+	vty->index = gsmnet_from_vty(vty);
+	vty->node = GSMNET_NODE;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_ncc,
+      cfg_net_ncc_cmd,
+      "network country code <1-999>",
+      "Set the GSM network country code")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->country_code = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mnc,
+      cfg_net_mnc_cmd,
+      "mobile network code <1-999>",
+      "Set the GSM mobile network code")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->network_code = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_short,
+      cfg_net_name_short_cmd,
+      "short name NAME",
+      "Set the short GSM network name")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	bsc_replace_string(gsmnet, &gsmnet->name_short, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_long,
+      cfg_net_name_long_cmd,
+      "long name NAME",
+      "Set the long GSM network name")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	bsc_replace_string(gsmnet, &gsmnet->name_long, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_auth_policy,
+      cfg_net_auth_policy_cmd,
+      "auth policy (closed|accept-all|token)",
+	"Authentication (not cryptographic)\n"
+	"Set the GSM network authentication policy\n"
+	"Require the MS to be activated in HLR\n"
+	"Accept all MS, whether in HLR or not\n"
+	"Use SMS-token based authentication\n")
+{
+	enum gsm_auth_policy policy = gsm_auth_policy_parse(argv[0]);
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->auth_policy = policy;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_reject_cause,
+      cfg_net_reject_cause_cmd,
+      "location updating reject cause <2-111>",
+      "Set the reject cause of location updating reject\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->reject_cause = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_encryption,
+      cfg_net_encryption_cmd,
+      "encryption a5 (0|1|2)",
+	"Encryption options\n"
+	"A5 encryption\n" "A5/0: No encryption\n"
+	"A5/1: Encryption\n" "A5/2: Export-grade Encryption\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->a5_encryption= atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_neci,
+      cfg_net_neci_cmd,
+      "neci (0|1)",
+	"New Establish Cause Indication\n"
+	"Don't set the NECI bit\n" "Set the NECI bit\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->neci = atoi(argv[0]);
+	gsm_net_update_ctype(gsmnet);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd,
+      "rrlp mode (none|ms-based|ms-preferred|ass-preferred)",
+	"Radio Resource Location Protocol\n"
+	"Set the Radio Resource Location Protocol Mode\n"
+	"Don't send RRLP request\n"
+	"Request MS-based location\n"
+	"Request any location, prefer MS-based\n"
+	"Request any location, prefer MS-assisted\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->rrlp.mode = rrlp_mode_parse(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mm_info, cfg_net_mm_info_cmd,
+      "mm info (0|1)",
+	"Whether to send MM INFO after LOC UPD ACCEPT")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->send_mm_info = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+#define HANDOVER_STR	"Handover Options\n"
+
+DEFUN(cfg_net_handover, cfg_net_handover_cmd,
+      "handover (0|1)",
+	HANDOVER_STR
+	"Don't perform in-call handover\n"
+	"Perform in-call handover\n")
+{
+	int enable = atoi(argv[0]);
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	if (enable && ipacc_rtp_direct) {
+		vty_out(vty, "%% Cannot enable handover unless RTP Proxy mode "
+			"is enabled by using the -P command line option%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	gsmnet->handover.active = enable;
+
+	return CMD_SUCCESS;
+}
+
+#define HO_WIN_STR HANDOVER_STR "Measurement Window\n"
+#define HO_WIN_RXLEV_STR HO_WIN_STR "Received Level Averaging\n"
+#define HO_WIN_RXQUAL_STR HO_WIN_STR "Received Quality Averaging\n"
+#define HO_PBUDGET_STR HANDOVER_STR "Power Budget\n"
+
+DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd,
+      "handover window rxlev averaging <1-10>",
+	HO_WIN_RXLEV_STR
+	"How many RxLev measurements are used for averaging")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.win_rxlev_avg = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd,
+      "handover window rxqual averaging <1-10>",
+	HO_WIN_RXQUAL_STR
+	"How many RxQual measurements are used for averaging")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.win_rxqual_avg = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd,
+      "handover window rxlev neighbor averaging <1-10>",
+	HO_WIN_RXLEV_STR
+	"How many RxQual measurements are used for averaging")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd,
+      "handover power budget interval <1-99>",
+	HO_PBUDGET_STR
+	"How often to check if we have a better cell (SACCH frames)")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.pwr_interval = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd,
+      "handover power budget hysteresis <0-999>",
+	HO_PBUDGET_STR
+	"How many dB does a neighbor to be stronger to become a HO candidate")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.pwr_hysteresis = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd,
+      "handover maximum distance <0-9999>",
+	HANDOVER_STR
+	"How big is the maximum timing advance before HO is forced")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->handover.max_distance = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_pag_any_tch,
+      cfg_net_pag_any_tch_cmd,
+      "paging any use tch (0|1)",
+      "Assign a TCH when receiving a Paging Any request")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->pag_any_tch = atoi(argv[0]);
+	gsm_net_update_ctype(gsmnet);
+	return CMD_SUCCESS;
+}
+
+#define DECLARE_TIMER(number, doc) \
+    DEFUN(cfg_net_T##number,					\
+      cfg_net_T##number##_cmd,					\
+      "timer t" #number  " <0-65535>",				\
+      "Configure GSM Timers\n"					\
+      doc)							\
+{								\
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);	\
+	int value = atoi(argv[0]);				\
+								\
+	if (value < 0 || value > 65535) {			\
+		vty_out(vty, "Timer value %s out of range.%s",	\
+		        argv[0], VTY_NEWLINE);			\
+		return CMD_WARNING;				\
+	}							\
+								\
+	gsmnet->T##number = value;				\
+	return CMD_SUCCESS;					\
+}
+
+DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT.")
+DECLARE_TIMER(3103, "Set the timeout value for HANDOVER.")
+DECLARE_TIMER(3105, "Currently not used.")
+DECLARE_TIMER(3107, "Currently not used.")
+DECLARE_TIMER(3109, "Currently not used.")
+DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel.")
+DECLARE_TIMER(3113, "Set the time to try paging a subscriber.")
+DECLARE_TIMER(3115, "Currently not used.")
+DECLARE_TIMER(3117, "Currently not used.")
+DECLARE_TIMER(3119, "Currently not used.")
+DECLARE_TIMER(3122, "Waiting time (seconds) after IMM ASS REJECT")
+DECLARE_TIMER(3141, "Currently not used.")
+
+DEFUN(cfg_net_dtx,
+      cfg_net_dtx_cmd,
+      "dtx-used (0|1)",
+      "Enable the usage of DTX.\n"
+      "DTX is enabled/disabled")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->dtx_enabled = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_subscr_keep,
+      cfg_net_subscr_keep_cmd,
+      "subscriber-keep-in-ram (0|1)",
+      "Keep unused subscribers in RAM.\n"
+      "Delete unused subscribers\n" "Keep unused subscribers\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->keep_subscr = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+      cfg_bts_cmd,
+      "bts BTS_NR",
+      "Select a BTS to configure\n"
+	"BTS Number\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	int bts_nr = atoi(argv[0]);
+	struct gsm_bts *bts;
+
+	if (bts_nr > gsmnet->num_bts) {
+		vty_out(vty, "%% The next unused BTS number is %u%s",
+			gsmnet->num_bts, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (bts_nr == gsmnet->num_bts) {
+		/* allocate a new one */
+		bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_UNKNOWN,
+				    HARDCODED_TSC, HARDCODED_BSIC);
+	} else
+		bts = gsm_bts_num(gsmnet, bts_nr);
+
+	if (!bts) {
+		vty_out(vty, "%% Unable to allocate BTS %u%s",
+			gsmnet->num_bts, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->index = bts;
+	vty->index_sub = &bts->description;
+	vty->node = BTS_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_type,
+      cfg_bts_type_cmd,
+      "type TYPE",
+      "Set the BTS type\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc;
+
+	rc = gsm_set_bts_type(bts, parse_btstype(argv[0]));
+	if (rc < 0)
+		return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_band,
+      cfg_bts_band_cmd,
+      "band BAND",
+      "Set the frequency band of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int band = gsm_band_parse(argv[0]);
+
+	if (band < 0) {
+		vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+			band, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->band = band;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ci,
+      cfg_bts_ci_cmd,
+      "cell_identity <0-65535>",
+      "Set the Cell identity of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int ci = atoi(argv[0]);
+
+	if (ci < 0 || ci > 0xffff) {
+		vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s",
+			ci, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->cell_identity = ci;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_lac,
+      cfg_bts_lac_cmd,
+      "location_area_code <0-65535>",
+      "Set the Location Area Code (LAC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int lac = atoi(argv[0]);
+
+	if (lac < 0 || lac > 0xffff) {
+		vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+		vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->location_area_code = lac;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_tsc,
+      cfg_bts_tsc_cmd,
+      "training_sequence_code <0-255>",
+      "Set the Training Sequence Code (TSC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int tsc = atoi(argv[0]);
+
+	if (tsc < 0 || tsc > 0xff) {
+		vty_out(vty, "%% TSC %d is not in the valid range (0-255)%s",
+			tsc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->tsc = tsc;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_bsic,
+      cfg_bts_bsic_cmd,
+      "base_station_id_code <0-63>",
+      "Set the Base Station Identity Code (BSIC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int bsic = atoi(argv[0]);
+
+	if (bsic < 0 || bsic > 0x3f) {
+		vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s",
+			bsic, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->bsic = bsic;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_unit_id,
+      cfg_bts_unit_id_cmd,
+      "ip.access unit_id <0-65534> <0-255>",
+      "Set the ip.access BTS Unit ID of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int site_id = atoi(argv[0]);
+	int bts_id = atoi(argv[1]);
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->ip_access.site_id = site_id;
+	bts->ip_access.bts_id = bts_id;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_serno,
+      cfg_bts_serno_cmd,
+      "hsl serial-number STRING",
+      "Set the HSL Serial Number of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->type != GSM_BTS_TYPE_HSL_FEMTO) {
+		vty_out(vty, "%% BTS is not of HSL type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->hsl.serno = strtoul(argv[0], NULL, 10);
+
+	return CMD_SUCCESS;
+}
+
+#define OML_STR	"Organization & Maintenance Link\n"
+#define IPA_STR "ip.access Specific Options\n"
+
+DEFUN(cfg_bts_stream_id,
+      cfg_bts_stream_id_cmd,
+      "oml ip.access stream_id <0-255>",
+	OML_STR IPA_STR
+      "Set the ip.access Stream ID of the OML link of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int stream_id = atoi(argv[0]);
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->oml_tei = stream_id;
+
+	return CMD_SUCCESS;
+}
+
+#define OML_E1_STR OML_STR "E1 Line\n"
+
+DEFUN(cfg_bts_oml_e1,
+      cfg_bts_oml_e1_cmd,
+      "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+	OML_E1_STR
+      "E1 interface to be used for OML\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_oml_e1_tei,
+      cfg_bts_oml_e1_tei_cmd,
+      "oml e1 tei <0-63>",
+	OML_E1_STR
+      "Set the TEI to be used for OML")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->oml_tei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd,
+      "channel allocator (ascending|descending)",
+	"Channnel Allocator\n" "Channel Allocator\n"
+	"Allocate Timeslots and Transceivers in ascending order\n"
+	"Allocate Timeslots and Transceivers in descending order\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (!strcmp(argv[0], "ascending"))
+		bts->chan_alloc_reverse = 0;
+	else
+		bts->chan_alloc_reverse = 1;
+
+	return CMD_SUCCESS;
+}
+
+#define RACH_STR "Random Access Control Channel\n"
+
+DEFUN(cfg_bts_rach_tx_integer,
+      cfg_bts_rach_tx_integer_cmd,
+      "rach tx integer <0-15>",
+	RACH_STR
+      "Set the raw tx integer value in RACH Control parameters IE")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_max_trans,
+      cfg_bts_rach_max_trans_cmd,
+      "rach max transmission (1|2|4|7)",
+	RACH_STR
+      "Set the maximum number of RACH burst transmissions")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+#define NM_STR "Network Management\n"
+
+DEFUN(cfg_bts_rach_nm_b_thresh,
+      cfg_bts_rach_nm_b_thresh_cmd,
+      "rach nm busy threshold <0-255>",
+	RACH_STR NM_STR
+      "Set the NM Busy Threshold in dB")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_b_thresh = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_nm_ldavg,
+      cfg_bts_rach_nm_ldavg_cmd,
+      "rach nm load average <0-65535>",
+	RACH_STR NM_STR
+      "Set the NM Loadaverage Slots value")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->rach_ldavg_slots = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd,
+      "cell barred (0|1)",
+      "Should this cell be barred from access?")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.rach_control.cell_bar = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd,
+      "rach emergency call allowed (0|1)",
+      "Should this cell allow emergency calls?")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (atoi(argv[0]) == 0)
+		bts->si_common.rach_control.t2 |= 0x4;
+	else
+		bts->si_common.rach_control.t2 &= ~0x4;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd,
+      "ms max power <0-40>",
+      "Maximum transmit power of the MS")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->ms_max_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd,
+      "cell reselection hysteresis <0-14>",
+      "Cell Re-Selection Hysteresis in dB")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd,
+      "rxlev access min <0-63>",
+      "Minimum RxLev needed for cell access (better than -110dBm)")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd,
+	"cell bar qualify (0|1)",
+	"Cell Bar Qualify")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd,
+	"cell reselection offset <0-126>",
+	"Cell Re-Selection Offset in dB")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd,
+	"temporary offset <0-60>",
+	"Cell selection temporary negative offset in dB")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd,
+	"temporary offset infinite",
+	"Sets cell selection temporary negative offset to infinity")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.temp_offs = 7;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd,
+	"penalty time <20-620>",
+	"Cell selection penalty time in seconds (by 20s increments)")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd,
+	"penalty time reserved",
+	"Set cell selection penalty time to reserved value 31\n"
+		"(indicate that CELL_RESELECT_OFFSET is subtracted from C2 "
+		"and TEMPORARY_OFFSET is ignored)")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.cell_ro_sel_par.present = 1;
+	bts->si_common.cell_ro_sel_par.penalty_time = 31;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_per_loc_upd, cfg_bts_per_loc_upd_cmd,
+      "periodic location update <0-1530>",
+      "Periodic Location Updating Interval in Minutes")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.chan_desc.t3212 = atoi(argv[0]) / 6;
+
+	return CMD_SUCCESS;
+}
+
+#define GPRS_TEXT	"GPRS Packet Network\n"
+
+DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd,
+	"gprs cell bvci <2-65535>",
+	GPRS_TEXT
+	"GPRS Cell Settings\n"
+	"GPRS BSSGP VC Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.cell.bvci = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd,
+	"gprs nsei <0-65535>",
+	GPRS_TEXT
+	"GPRS NS Entity Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nse.nsei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \
+		"NSVC Logical Number\n"
+
+DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd,
+	"gprs nsvc <0-1> nsvci <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"NS Virtual Connection Identifier\n"
+	"GPRS NS VC Identifier")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].nsvci = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd,
+	"gprs nsvc <0-1> local udp port <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"GPRS NS Local UDP Port")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].local_port = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd,
+	"gprs nsvc <0-1> remote udp port <0-65535>",
+	GPRS_TEXT NSVC_TEXT
+	"GPRS NS Remote UDP Port")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.nsvc[idx].remote_port = atoi(argv[1]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd,
+	"gprs nsvc <0-1> remote ip A.B.C.D",
+	GPRS_TEXT NSVC_TEXT
+	"GPRS NS Remote IP Address")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = atoi(argv[0]);
+	struct in_addr ia;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	inet_aton(argv[1], &ia);
+	bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pag_free, cfg_bts_pag_free_cmd,
+      "paging free FREE_NR",
+      "Only page when having a certain amount of free slots. -1 to disable")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->paging.free_chans_need = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd,
+	"gprs ns timer " NS_TIMERS " <0-255>",
+	GPRS_TEXT "Network Service\n"
+	"Network Service Timer\n"
+	NS_TIMERS_HELP "Timer Value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer))
+		return CMD_WARNING;
+
+	bts->gprs.nse.timer[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)"
+#define BSSGP_TIMERS_HELP	\
+	"Tbvc-block timeout\n"			\
+	"Tbvc-block retries\n"			\
+	"Tbvc-unblock retries\n"		\
+	"Tbvcc-reset timeout\n"			\
+	"Tbvc-reset retries\n"			\
+	"Tbvc-suspend timeout\n"		\
+	"Tbvc-suspend retries\n"		\
+	"Tbvc-resume timeout\n"			\
+	"Tbvc-resume retries\n"			\
+	"Tbvc-capa-update timeout\n"		\
+	"Tbvc-capa-update retries\n"
+
+DEFUN(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd,
+	"gprs cell timer " BSSGP_TIMERS " <0-255>",
+	GPRS_TEXT "Cell / BSSGP\n"
+	"Cell/BSSGP Timer\n"
+	BSSGP_TIMERS_HELP "Timer Value\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer))
+		return CMD_WARNING;
+
+	bts->gprs.cell.timer[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd,
+	"gprs routing area <0-255>",
+	GPRS_TEXT
+	"GPRS Routing Area Code")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->gprs.mode == BTS_GPRS_NONE) {
+		vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.rac = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd,
+	"gprs mode (none|gprs|egprs)",
+	GPRS_TEXT
+	"GPRS Mode for this BTS\n"
+	"GPRS Disabled on this BTS\n"
+	"GPRS Enabled on this BTS\n"
+	"EGPRS (EDGE) Enabled on this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0]);
+
+	if (mode != BTS_GPRS_NONE &&
+	    !gsm_bts_has_feature(bts, BTS_FEAT_GPRS)) {
+		vty_out(vty, "This BTS type does not support %s%s", argv[0],
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (mode == BTS_GPRS_EGPRS &&
+	    !gsm_bts_has_feature(bts, BTS_FEAT_EGPRS)) {
+		vty_out(vty, "This BTS type does not support %s%s", argv[0],
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->gprs.mode = mode;
+
+	return CMD_SUCCESS;
+}
+
+#define SI_TEXT		"System Information Messages\n"
+#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)"
+#define SI_TYPE_HELP 	"System Information Type 1\n"	\
+			"System Information Type 2\n"	\
+			"System Information Type 3\n"	\
+			"System Information Type 4\n"	\
+			"System Information Type 5\n"	\
+			"System Information Type 6\n"	\
+			"System Information Type 7\n"	\
+			"System Information Type 8\n"	\
+			"System Information Type 9\n"	\
+			"System Information Type 10\n"	\
+			"System Information Type 13\n"	\
+			"System Information Type 16\n"	\
+			"System Information Type 17\n"	\
+			"System Information Type 18\n"	\
+			"System Information Type 19\n"	\
+			"System Information Type 20\n"	\
+			"System Information Type 2bis\n"	\
+			"System Information Type 2ter\n"	\
+			"System Information Type 2quater\n"	\
+			"System Information Type 5bis\n"	\
+			"System Information Type 5ter\n"
+
+DEFUN(cfg_bts_si_mode, cfg_bts_si_mode_cmd,
+	"system-information " SI_TYPE_TEXT " mode (static|computed)",
+	SI_TEXT SI_TYPE_HELP
+	"System Information Mode\n"
+	"Static user-specified\n"
+	"Dynamic, BSC-computed\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int type;
+
+	type = get_string_value(osmo_sitype_strs, argv[0]);
+	if (type < 0) {
+		vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[1], "static"))
+		bts->si_mode_static |= (1 << type);
+	else
+		bts->si_mode_static &= ~(1 << type);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si_static, cfg_bts_si_static_cmd,
+	"system-information " SI_TYPE_TEXT " static HEXSTRING",
+	SI_TEXT SI_TYPE_HELP
+	"Static System Information filling\n"
+	"Static user-specified SI content in HEX notation\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc, type;
+
+	type = get_string_value(osmo_sitype_strs, argv[0]);
+	if (type < 0) {
+		vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!(bts->si_mode_static & (1 << type))) {
+		vty_out(vty, "SI Type %s is not configured in static mode%s",
+			get_value_string(osmo_sitype_strs, type), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Fill buffer with padding pattern */
+	memset(bts->si_buf[type], 0x2b, sizeof(bts->si_buf[type]));
+
+	/* Parse the user-specified SI in hex format, [partially] overwriting padding */
+	rc = hexparse(argv[1], bts->si_buf[type], sizeof(bts->si_buf[0]));
+	if (rc < 0 || rc > sizeof(bts->si_buf[0])) {
+		vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Mark this SI as present */
+	bts->si_valid |= (1 << type);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd,
+	"neighbor-list mode (automatic|manual|manual-si5)",
+	"Neighbor List\n" "Mode of Neighbor List generation\n"
+	"Automatically from all BTS in this OpenBSC\n" "Manual\n"
+	"Manual with different lists for SI2 and SI5\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int mode = get_string_value(bts_neigh_mode_strs, argv[0]);
+
+	switch (mode) {
+	case NL_MODE_MANUAL_SI5SEP:
+	case NL_MODE_MANUAL:
+		/* make sure we clear the current list when switching to
+		 * manual mode */
+		if (bts->neigh_list_manual_mode == 0)
+			memset(&bts->si_common.data.neigh_list, 0,
+				sizeof(bts->si_common.data.neigh_list));
+		break;
+	default:
+		break;
+	}
+
+	bts->neigh_list_manual_mode = mode;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_neigh, cfg_bts_neigh_cmd,
+	"neighbor-list (add|del) arfcn <0-1024>",
+	"Neighbor List\n" "Add to manual neighbor list\n"
+	"Delete from manual neighbor list\n" "ARFCN of neighbor\n"
+	"ARFCN of neighbor\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct bitvec *bv = &bts->si_common.neigh_list;
+	uint16_t arfcn = atoi(argv[1]);
+
+	if (!bts->neigh_list_manual_mode) {
+		vty_out(vty, "%% Cannot configure neighbor list in "
+			"automatic mode%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "add"))
+		bitvec_set_bit_pos(bv, arfcn, 1);
+	else
+		bitvec_set_bit_pos(bv, arfcn, 0);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd,
+	"si5 neighbor-list (add|del) arfcn <0-1024>",
+	"SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n"
+	"Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n"
+	"ARFCN of neighbor\n")
+{
+	struct gsm_bts *bts = vty->index;
+	struct bitvec *bv = &bts->si_common.si5_neigh_list;
+	uint16_t arfcn = atoi(argv[1]);
+
+	if (!bts->neigh_list_manual_mode) {
+		vty_out(vty, "%% Cannot configure neighbor list in "
+			"automatic mode%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[0], "add"))
+		bitvec_set_bit_pos(bv, arfcn, 1);
+	else
+		bitvec_set_bit_pos(bv, arfcn, 0);
+
+	return CMD_SUCCESS;
+}
+
+#define TRX_TEXT "Radio Transceiver\n"
+
+/* per TRX configuration */
+DEFUN(cfg_trx,
+      cfg_trx_cmd,
+      "trx TRX_NR",
+	TRX_TEXT
+      "Select a TRX to configure")
+{
+	int trx_nr = atoi(argv[0]);
+	struct gsm_bts *bts = vty->index;
+	struct gsm_bts_trx *trx;
+
+	if (trx_nr > bts->num_trx) {
+		vty_out(vty, "%% The next unused TRX number in this BTS is %u%s",
+			bts->num_trx, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (trx_nr == bts->num_trx) {
+		/* we need to allocate a new one */
+		trx = gsm_bts_trx_alloc(bts);
+	} else
+		trx = gsm_bts_trx_num(bts, trx_nr);
+
+	if (!trx)
+		return CMD_WARNING;
+
+	vty->index = trx;
+	vty->index_sub = &trx->description;
+	vty->node = TRX_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_arfcn,
+      cfg_trx_arfcn_cmd,
+      "arfcn <0-1024>",
+      "Set the ARFCN for this TRX\n")
+{
+	int arfcn = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+
+	/* FIXME: check if this ARFCN is supported by this TRX */
+
+	trx->arfcn = arfcn;
+
+	/* FIXME: patch ARFCN into SYSTEM INFORMATION */
+	/* FIXME: use OML layer to update the ARFCN */
+	/* FIXME: use RSL layer to update SYSTEM INFORMATION */
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power,
+      cfg_trx_nominal_power_cmd,
+      "nominal power <0-100>",
+      "Nominal TRX RF Power in dB\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	trx->nominal_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_max_power_red,
+      cfg_trx_max_power_red_cmd,
+      "max_power_red <0-100>",
+      "Reduction of maximum BS RF Power in dB\n")
+{
+	int maxpwr_r = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+	int upper_limit = 24;	/* default 12.21 max power red. */
+
+	/* FIXME: check if our BTS type supports more than 12 */
+	if (maxpwr_r < 0 || maxpwr_r > upper_limit) {
+		vty_out(vty, "%% Power %d dB is not in the valid range%s",
+			maxpwr_r, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (maxpwr_r & 1) {
+		vty_out(vty, "%% Power %d dB is not an even value%s",
+			maxpwr_r, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	trx->max_power_red = maxpwr_r;
+
+	/* FIXME: make sure we update this using OML */
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rsl_e1,
+      cfg_trx_rsl_e1_cmd,
+      "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "E1 interface to be used for RSL\n")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rsl_e1_tei,
+      cfg_trx_rsl_e1_tei_cmd,
+      "rsl e1 tei <0-63>",
+      "Set the TEI to be used for RSL")
+{
+	struct gsm_bts_trx *trx = vty->index;
+
+	trx->rsl_tei = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rf_locked,
+      cfg_trx_rf_locked_cmd,
+      "rf_locked (0|1)",
+      "Turn off RF of the TRX.\n")
+{
+	int locked = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+
+	gsm_trx_lock_rf(trx, locked);
+	return CMD_SUCCESS;
+}
+
+/* per TS configuration */
+DEFUN(cfg_ts,
+      cfg_ts_cmd,
+      "timeslot <0-7>",
+      "Select a Timeslot to configure")
+{
+	int ts_nr = atoi(argv[0]);
+	struct gsm_bts_trx *trx = vty->index;
+	struct gsm_bts_trx_ts *ts;
+
+	if (ts_nr >= TRX_NR_TS) {
+		vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s",
+			TRX_NR_TS, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts = &trx->ts[ts_nr];
+
+	vty->index = ts;
+	vty->node = TS_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_pchan,
+      cfg_ts_pchan_cmd,
+      "phys_chan_config PCHAN",
+      "Physical Channel configuration (TCH/SDCCH/...)")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int pchanc;
+
+	pchanc = gsm_pchan_parse(argv[0]);
+	if (pchanc < 0)
+		return CMD_WARNING;
+
+	ts->pchan = pchanc;
+
+	return CMD_SUCCESS;
+}
+
+#define HOPPING_STR "Configure frequency hopping\n"
+
+DEFUN(cfg_ts_hopping,
+      cfg_ts_hopping_cmd,
+      "hopping enabled (0|1)",
+	HOPPING_STR "Enable or disable frequency hopping\n"
+      "Disable frequency hopping\n" "Enable frequency hopping\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int enabled = atoi(argv[0]);
+
+	if (enabled && !gsm_bts_has_feature(ts->trx->bts, BTS_FEAT_HOPPING)) {
+		vty_out(vty, "BTS model does not support hopping%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts->hopping.enabled = enabled;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_hsn,
+      cfg_ts_hsn_cmd,
+      "hopping sequence-number <0-63>",
+	HOPPING_STR
+      "Which hopping sequence to use for this channel")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	ts->hopping.hsn = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_maio,
+      cfg_ts_maio_cmd,
+      "hopping maio <0-63>",
+	HOPPING_STR
+      "Which hopping MAIO to use for this channel")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	ts->hopping.maio = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_arfcn_add,
+      cfg_ts_arfcn_add_cmd,
+      "hopping arfcn add <0-1023>",
+	HOPPING_STR "Configure hopping ARFCN list\n"
+      "Add an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int arfcn = atoi(argv[0]);
+
+	bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_arfcn_del,
+      cfg_ts_arfcn_del_cmd,
+      "hopping arfcn del <0-1023>",
+	HOPPING_STR "Configure hopping ARFCN list\n"
+      "Delete an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+	int arfcn = atoi(argv[0]);
+
+	bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_e1_subslot,
+      cfg_ts_e1_subslot_cmd,
+      "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+      "E1 sub-slot connected to this on-air timeslot")
+{
+	struct gsm_bts_trx_ts *ts = vty->index;
+
+	parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]);
+
+	return CMD_SUCCESS;
+}
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
+{
+	vty_out(vty, "Channel Requests        : %lu total, %lu no channel%s",
+		counter_get(net->stats.chreq.total),
+		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
+	vty_out(vty, "Channel Failures        : %lu rf_failures, %lu rll failures%s",
+		counter_get(net->stats.chan.rf_fail),
+		counter_get(net->stats.chan.rll_err), VTY_NEWLINE);
+	vty_out(vty, "Paging                  : %lu attempted, %lu complete, %lu expired%s",
+		counter_get(net->stats.paging.attempted),
+		counter_get(net->stats.paging.completed),
+		counter_get(net->stats.paging.expired), VTY_NEWLINE);
+	vty_out(vty, "BTS failures            : %lu OML, %lu RSL%s",
+		counter_get(net->stats.bts.oml_fail),
+		counter_get(net->stats.bts.rsl_fail), VTY_NEWLINE);
+}
+
+DEFUN(logging_fltr_imsi,
+      logging_fltr_imsi_cmd,
+      "logging filter imsi IMSI",
+	LOGGING_STR FILTER_STR
+      "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_set_imsi_filter(tgt, argv[0]);
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(drop_bts,
+      drop_bts_cmd,
+      "drop bts connection <0-65535> (oml|rsl)",
+      "Debug/Simulation command to drop ipaccess BTS\n"
+      "BTS NR\n" "Connection Type\n")
+{
+	struct gsm_network *gsmnet;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts *bts;
+	unsigned int bts_nr;
+
+	gsmnet = gsmnet_from_vty(vty);
+
+	bts_nr = atoi(argv[0]);
+	if (bts_nr >= gsmnet->num_bts) {
+		vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+			gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts = gsm_bts_num(gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+
+	/* close all connections */
+	if (strcmp(argv[1], "oml") == 0) {
+		ipaccess_drop_oml(bts);
+	} else if (strcmp(argv[1], "rsl") == 0) {
+		/* close all rsl connections */
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			ipaccess_drop_rsl(trx);
+		}
+	} else {
+		vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(pdch_act, pdch_act_cmd,
+	"bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)",
+	"BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+	"TRX Timeslot\n" "Timeslot Number\n" "Packet Data Channel\n"
+	"Activate Dynamic PDCH/TCH (-> PDCH mode)\n"
+	"Deactivate Dynamic PDCH/TCH (-> TCH mode)\n")
+{
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	int bts_nr = atoi(argv[0]);
+	int trx_nr = atoi(argv[1]);
+	int ts_nr = atoi(argv[2]);
+	int activate;
+
+	bts = gsm_bts_num(bsc_gsmnet, bts_nr);
+	if (!bts) {
+		vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% This command only works for ipaccess BTS%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	trx = gsm_bts_trx_num(bts, trx_nr);
+	if (!trx) {
+		vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts = &trx->ts[ts_nr];
+	if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+		vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH "
+			"mode%s", ts_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[3], "activate"))
+		activate = 1;
+	else
+		activate = 0;
+
+	rsl_ipacc_pdch_activate(ts, activate);
+
+	return CMD_SUCCESS;
+
+}
+
+extern int bsc_vty_init_extra(void);
+extern const char *openbsc_copyright;
+
+int bsc_vty_init(void)
+{
+	install_element_ve(&show_net_cmd);
+	install_element_ve(&show_bts_cmd);
+	install_element_ve(&show_trx_cmd);
+	install_element_ve(&show_ts_cmd);
+	install_element_ve(&show_lchan_cmd);
+	install_element_ve(&show_lchan_summary_cmd);
+	install_element_ve(&logging_fltr_imsi_cmd);
+
+	install_element_ve(&show_e1drv_cmd);
+	install_element_ve(&show_e1line_cmd);
+	install_element_ve(&show_e1ts_cmd);
+
+	install_element_ve(&show_paging_cmd);
+
+	logging_vty_add_cmds();
+	install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+	install_element(CONFIG_NODE, &cfg_net_cmd);
+	install_node(&net_node, config_write_net);
+	install_default(GSMNET_NODE);
+	install_element(GSMNET_NODE, &ournode_exit_cmd);
+	install_element(GSMNET_NODE, &ournode_end_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ncc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mnc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_short_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_long_cmd);
+	install_element(GSMNET_NODE, &cfg_net_auth_policy_cmd);
+	install_element(GSMNET_NODE, &cfg_net_reject_cause_cmd);
+	install_element(GSMNET_NODE, &cfg_net_encryption_cmd);
+	install_element(GSMNET_NODE, &cfg_net_neci_cmd);
+	install_element(GSMNET_NODE, &cfg_net_rrlp_mode_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mm_info_cmd);
+	install_element(GSMNET_NODE, &cfg_net_handover_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxqual_avg_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_neigh_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3101_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3103_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3105_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3107_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3109_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3111_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3113_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3115_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3117_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3119_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3122_cmd);
+	install_element(GSMNET_NODE, &cfg_net_T3141_cmd);
+	install_element(GSMNET_NODE, &cfg_net_dtx_cmd);
+	install_element(GSMNET_NODE, &cfg_net_subscr_keep_cmd);
+	install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd);
+
+	install_element(GSMNET_NODE, &cfg_bts_cmd);
+	install_node(&bts_node, config_write_bts);
+	install_default(BTS_NODE);
+	install_element(BTS_NODE, &ournode_exit_cmd);
+	install_element(BTS_NODE, &ournode_end_cmd);
+	install_element(BTS_NODE, &cfg_bts_type_cmd);
+	install_element(BTS_NODE, &cfg_description_cmd);
+	install_element(BTS_NODE, &cfg_no_description_cmd);
+	install_element(BTS_NODE, &cfg_bts_band_cmd);
+	install_element(BTS_NODE, &cfg_bts_ci_cmd);
+	install_element(BTS_NODE, &cfg_bts_lac_cmd);
+	install_element(BTS_NODE, &cfg_bts_tsc_cmd);
+	install_element(BTS_NODE, &cfg_bts_bsic_cmd);
+	install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+	install_element(BTS_NODE, &cfg_bts_serno_cmd);
+	install_element(BTS_NODE, &cfg_bts_stream_id_cmd);
+	install_element(BTS_NODE, &cfg_bts_oml_e1_cmd);
+	install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd);
+	install_element(BTS_NODE, &cfg_bts_challoc_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
+	install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd);
+	install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
+	install_element(BTS_NODE, &cfg_bts_per_loc_upd_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
+	install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd);
+	install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd);
+	install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd);
+	install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd);
+	install_element(BTS_NODE, &cfg_bts_penalty_time_cmd);
+	install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd);
+	install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd);
+	install_element(BTS_NODE, &cfg_bts_pag_free_cmd);
+	install_element(BTS_NODE, &cfg_bts_si_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_si_static_cmd);
+	install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd);
+	install_element(BTS_NODE, &cfg_bts_neigh_cmd);
+	install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd);
+
+	install_element(BTS_NODE, &cfg_trx_cmd);
+	install_node(&trx_node, dummy_config_write);
+	install_default(TRX_NODE);
+	install_element(TRX_NODE, &ournode_exit_cmd);
+	install_element(TRX_NODE, &ournode_end_cmd);
+	install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
+	install_element(TRX_NODE, &cfg_description_cmd);
+	install_element(TRX_NODE, &cfg_no_description_cmd);
+	install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+	install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
+	install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
+
+	install_element(TRX_NODE, &cfg_ts_cmd);
+	install_node(&ts_node, dummy_config_write);
+	install_default(TS_NODE);
+	install_element(TS_NODE, &ournode_exit_cmd);
+	install_element(TS_NODE, &ournode_end_cmd);
+	install_element(TS_NODE, &cfg_ts_pchan_cmd);
+	install_element(TS_NODE, &cfg_ts_hopping_cmd);
+	install_element(TS_NODE, &cfg_ts_hsn_cmd);
+	install_element(TS_NODE, &cfg_ts_maio_cmd);
+	install_element(TS_NODE, &cfg_ts_arfcn_add_cmd);
+	install_element(TS_NODE, &cfg_ts_arfcn_del_cmd);
+	install_element(TS_NODE, &cfg_ts_e1_subslot_cmd);
+
+	install_element(ENABLE_NODE, &drop_bts_cmd);
+	install_element(ENABLE_NODE, &pdch_act_cmd);
+
+	abis_nm_vty_init();
+	abis_om2k_vty_init();
+	e1inp_vty_init();
+
+	bsc_vty_init_extra();
+
+	return 0;
+}
diff --git a/src/libbsc/bts_ericsson_rbs2000.c b/src/libbsc/bts_ericsson_rbs2000.c
new file mode 100644
index 0000000..27d5ce6
--- /dev/null
+++ b/src/libbsc/bts_ericsson_rbs2000.c
@@ -0,0 +1,164 @@
+/* Ericsson RBS-2xxx specific code */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <osmocore/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+
+#include "../libabis/input/lapd.h"
+
+static void bootstrap_om_bts(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+	abis_om2k_tx_start_req(bts, &om2k_mo_cf);
+	/* FIXME */
+}
+
+static void bootstrap_om_trx(struct gsm_bts_trx *trx)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n",
+	     trx->bts->nr, trx->nr);
+	/* FIXME */
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+	/* FIXME */
+	return 0;
+}
+
+
+/* Tell LAPD to start start the SAP (send SABM requests) for all signalling
+ * timeslots in this line */
+static void start_sabm_in_line(struct e1inp_line *line, int start)
+{
+	struct e1inp_sign_link *link;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
+		struct e1inp_ts *ts = &line->ts[i];
+
+		if (ts->type != E1INP_TS_TYPE_SIGN)
+			continue;
+
+		llist_for_each_entry(link, &ts->sign.sign_links, list) {
+			if (start)
+				lapd_sap_start(ts->driver.dahdi.lapd, link->tei, link->sapi);
+			else
+				lapd_sap_stop(ts->driver.dahdi.lapd, link->tei, link->sapi);
+		}
+	}
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts *bts;
+
+	if (subsys != SS_GLOBAL)
+		return 0;
+
+	switch (signal) {
+	case S_GLOBAL_BTS_CLOSE_OM:
+		bts = signal_data;
+		if (bts->type == GSM_BTS_TYPE_RBS2000)
+			shutdown_om(signal_data);
+		break;
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+
+	if (subsys != SS_INPUT)
+		return 0;
+
+	switch (signal) {
+	case S_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000)
+				break;
+			if (isd->tei == isd->trx->bts->oml_tei)
+				bootstrap_om_bts(isd->trx->bts);
+			else
+				bootstrap_om_trx(isd->trx);
+			break;
+		}
+		break;
+	case S_INP_LINE_INIT:
+		/* Right now Ericsson RBS are only supported on DAHDI */
+		if (strcasecmp(isd->line->driver->name, "DAHDI"))
+			break;
+		start_sabm_in_line(isd->line, 1);
+		break;
+	case S_INP_LINE_ALARM:
+		if (strcasecmp(isd->line->driver->name, "DAHDI"))
+			break;
+		start_sabm_in_line(isd->line, 0);
+		break;
+	case S_INP_LINE_NOALARM:
+		if (strcasecmp(isd->line->driver->name, "DAHDI"))
+			break;
+		start_sabm_in_line(isd->line, 1);
+		break;
+	}
+
+	return 0;
+}
+
+static void config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	abis_om2k_config_write_bts(vty, bts);
+}
+
+static struct gsm_bts_model model_rbs2k = {
+	.type = GSM_BTS_TYPE_RBS2000,
+	.name = "rbs2000",
+	.oml_rcvmsg = &abis_om2k_rcvmsg,
+	.config_write_bts = &config_write_bts,
+};
+
+int bts_model_rbs2k_init(void)
+{
+	model_rbs2k.features.data = &model_rbs2k._features_data[0];
+	model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data);
+
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HSCSD);
+
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+	register_signal_handler(SS_GLOBAL, gbl_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_rbs2k);
+}
diff --git a/src/libbsc/bts_hsl_femtocell.c b/src/libbsc/bts_hsl_femtocell.c
new file mode 100644
index 0000000..e01634c
--- /dev/null
+++ b/src/libbsc/bts_hsl_femtocell.c
@@ -0,0 +1,162 @@
+/* OpenBSC support code for HSL Femtocell */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2011 by OnWaves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/tlv.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/e1_input.h>
+
+static struct gsm_bts_model model_hslfemto = {
+	.type = GSM_BTS_TYPE_HSL_FEMTO,
+	.nm_att_tlvdef = {
+		.def = {
+			/* no HSL specific OML attributes that we know of */
+		},
+	},
+};
+
+
+static const uint8_t l1_msg[] = {
+#ifdef HSL_SR_1_0
+	0x80, 0x8a,
+#else
+	0x81, 0x8a,
+#endif
+		0xC4, 0x0b,
+};
+
+static const uint8_t conn_trau_msg[] = {
+#ifdef HSL_SR_1_0
+	0x80, 0x81,
+#else
+	0x81, 0x81,
+#endif
+		0xC1, 16,
+			0x02, 0x00, 0x00, 0x00, 0xC0, 0xA8, 0xEA, 0x01,
+			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t conn_trau_msg2[] = {
+#ifdef HSL_SR_1_0
+	0x80, 0x81,
+#else
+	0x81, 0x81,
+#endif
+		0xC1, 16,
+			0x02, 0x00, 0xd4, 0x07, 0xC0, 0xA8, 0xEA, 0x01,
+			0x38, 0xA4, 0x45, 0x00, 0x04, 0x59, 0x40, 0x00
+};
+
+static uint8_t oml_arfcn_bsic[] = {
+#ifdef HSL_SR_1_0
+	0x81, 0x80, 0x00, 10,
+#else
+	0x80, 0x80, 0x00, 10,
+#endif
+		NM_MT_SET_BTS_ATTR, NM_OC_BTS, 0xff, 0xff, 0xff,
+			NM_ATT_BCCH_ARFCN, 0x03, 0x67,
+			NM_ATT_BSIC, 0x00
+};
+
+static inline struct msgb *hsl_alloc_msgb(void)
+{
+	return msgb_alloc_headroom(1024, 127, "HSL");
+}
+
+static int hslfemto_bootstrap_om(struct gsm_bts *bts)
+{
+	struct msgb *msg;
+	uint8_t *cur;
+
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(l1_msg));
+	memcpy(msg->data, l1_msg, sizeof(l1_msg));
+	msg->trx = bts->c0;
+	abis_rsl_sendmsg(msg);
+
+#if 1
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(conn_trau_msg));
+	memcpy(msg->data, conn_trau_msg, sizeof(conn_trau_msg));
+	msg->trx = bts->c0;
+	abis_rsl_sendmsg(msg);
+#endif
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(conn_trau_msg2));
+	memcpy(msg->data, conn_trau_msg2, sizeof(conn_trau_msg2));
+	msg->trx = bts->c0;
+	abis_rsl_sendmsg(msg);
+
+	*((uint16_t *)oml_arfcn_bsic+10) = htons(bts->c0->arfcn);
+	oml_arfcn_bsic[13] = bts->bsic;
+
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(oml_arfcn_bsic));
+	memcpy(msg->data, oml_arfcn_bsic, sizeof(oml_arfcn_bsic));
+	msg->trx = bts->c0;
+	_abis_nm_sendmsg(msg, 0);
+
+	/* Delay the OPSTART until after SI have been set via RSL */
+	//abis_nm_opstart(bts, NM_OC_BTS, 255, 255, 255);
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+
+	if (subsys != SS_INPUT)
+		return 0;
+
+	switch (signal) {
+	case S_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			hslfemto_bootstrap_om(isd->trx->bts);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int bts_model_hslfemto_init(void)
+{
+	model_hslfemto.features.data = &model_hslfemto._features_data[0];
+	model_hslfemto.features.data_len = sizeof(model_hslfemto._features_data);
+
+	gsm_btsmodel_set_feature(&model_hslfemto, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&model_hslfemto, BTS_FEAT_EGPRS);
+
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_hslfemto);
+}
diff --git a/src/libbsc/bts_ipaccess_nanobts.c b/src/libbsc/bts_ipaccess_nanobts.c
new file mode 100644
index 0000000..25dc0c8
--- /dev/null
+++ b/src/libbsc/bts_ipaccess_nanobts.c
@@ -0,0 +1,447 @@
+/* ip.access nanoBTS specific code */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocore/tlv.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_nanobts = {
+	.type = GSM_BTS_TYPE_NANOBTS,
+	.name = "nanobts",
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.nm_att_tlvdef = {
+		.def = {
+			/* ip.access specifics */
+			[NM_ATT_IPACC_DST_IP] =		{ TLV_TYPE_FIXED, 4 },
+			[NM_ATT_IPACC_DST_IP_PORT] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_STREAM_ID] =	{ TLV_TYPE_TV, },
+			[NM_ATT_IPACC_SEC_OML_CFG] =	{ TLV_TYPE_FIXED, 6 },
+			[NM_ATT_IPACC_IP_IF_CFG] =	{ TLV_TYPE_FIXED, 8 },
+			[NM_ATT_IPACC_IP_GW_CFG] =	{ TLV_TYPE_FIXED, 12 },
+			[NM_ATT_IPACC_IN_SERV_TIME] =	{ TLV_TYPE_FIXED, 4 },
+			[NM_ATT_IPACC_LOCATION] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_PAGING_CFG] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_UNIT_ID] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_UNIT_NAME] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SNMP_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NV_FLAGS] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_FREQ_CTRL] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CUR_SW_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_TIMING_BUS] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CGI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RAC] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_OBJ_VERSION] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NSEI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_BVCI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NSVCI] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NS_CFG] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_BSSGP_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_NS_LINK_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_ALM_THRESH_LIST]=	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_TIB_CONTROL] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SUPP_FEATURES] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_CODING_SCHEMES] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG_2] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_HEARTB_TOUT] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_UPTIME] =		{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_RLC_CFG_3] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SSL_CFG] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_SEC_POSSIBLE] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_IML_SSL_STATE] =	{ TLV_TYPE_TL16V },
+			[NM_ATT_IPACC_REVOC_DATE] =	{ TLV_TYPE_TL16V },
+		},
+	},
+};
+
+static unsigned char nanobts_attr_bts[] = {
+	NM_ATT_INTERF_BOUND, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x73,
+	/* interference avg. period in numbers of SACCH multifr */
+	NM_ATT_INTAVE_PARAM, 0x06,
+	/* conn fail based on SACCH error rate */
+	NM_ATT_CONN_FAIL_CRIT, 0x00, 0x02, 0x01, 0x10,
+	NM_ATT_T200, 0x1e, 0x24, 0x24, 0xa8, 0x34, 0x21, 0xa8,
+	NM_ATT_MAX_TA, 0x3f,
+	NM_ATT_OVERL_PERIOD, 0x00, 0x01, 10, /* seconds */
+	NM_ATT_CCCH_L_T, 10, /* percent */
+	NM_ATT_CCCH_L_I_P, 1, /* seconds */
+	NM_ATT_RACH_B_THRESH, 10, /* busy threshold in - dBm */
+	NM_ATT_LDAVG_SLOTS, 0x03, 0xe8, /* rach load averaging 1000 slots */
+	NM_ATT_BTS_AIR_TIMER, 128, /* miliseconds */
+	NM_ATT_NY1, 10, /* 10 retransmissions of physical config */
+	NM_ATT_BCCH_ARFCN, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff,
+	NM_ATT_BSIC, HARDCODED_BSIC,
+	NM_ATT_IPACC_CGI, 0, 7,  0x00, 0xf1, 0x10, 0x00, 0x01, 0x00, 0x00,
+};
+
+static unsigned char nanobts_attr_radio[] = {
+	NM_ATT_RF_MAXPOWR_R, 0x0c, /* number of -2dB reduction steps / Pn */
+	NM_ATT_ARFCN_LIST, 0x00, 0x02, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff,
+};
+
+static unsigned char nanobts_attr_nse[] = {
+	NM_ATT_IPACC_NSEI, 0, 2,  0x03, 0x9d, /* NSEI 925 */
+	NM_ATT_IPACC_NS_CFG, 0, 7,  3,  /* (un)blocking timer (Tns-block) */
+				    3,  /* (un)blocking retries */
+				    3,  /* reset timer (Tns-reset) */
+				    3,  /* reset retries */
+				    30,  /* test timer (Tns-test) */
+				    3,  /* alive timer (Tns-alive) */
+				    10, /* alive retrires */
+	NM_ATT_IPACC_BSSGP_CFG, 0, 11,
+				    3,  /* blockimg timer (T1) */
+				    3,  /* blocking retries */
+				    3,  /* unblocking retries */
+				    3,  /* reset timer */
+				    3,  /* reset retries */
+				    10, /* suspend timer (T3) in 100ms */
+				    3,  /* suspend retries */
+				    10, /* resume timer (T4) in 100ms */
+				    3,  /* resume retries */
+				    10, /* capability update timer (T5) */
+				    3,  /* capability update retries */
+};
+
+static unsigned char nanobts_attr_cell[] = {
+	NM_ATT_IPACC_RAC, 0, 1,  1, /* routing area code */
+	NM_ATT_IPACC_GPRS_PAGING_CFG, 0, 2,
+		5,	/* repeat time (50ms) */
+		3,	/* repeat count */
+	NM_ATT_IPACC_BVCI, 0, 2,  0x03, 0x9d, /* BVCI 925 */
+	NM_ATT_IPACC_RLC_CFG, 0, 9,
+		20, 	/* T3142 */
+		5, 	/* T3169 */
+		5,	/* T3191 */
+		200,	/* T3193 */
+		5,	/* T3195 */
+		10,	/* N3101 */
+		4,	/* N3103 */
+		8,	/* N3105 */
+		15,	/* RLC CV countdown */
+	NM_ATT_IPACC_CODING_SCHEMES, 0, 2,  0x0f, 0x00,	/* CS1..CS4 */
+	NM_ATT_IPACC_RLC_CFG_2, 0, 5,
+		0x00, 250,	/* T downlink TBF extension (0..500) */
+		0x00, 250,	/* T uplink TBF extension (0..500) */
+		2,	/* CS2 */
+#if 0
+	/* EDGE model only, breaks older models.
+	 * Should inquire the BTS capabilities */
+	NM_ATT_IPACC_RLC_CFG_3, 0, 1,
+		2,	/* MCS2 */
+#endif
+};
+
+static unsigned char nanobts_attr_nsvc0[] = {
+	NM_ATT_IPACC_NSVCI, 0, 2,  0x03, 0x9d, /* 925 */
+	NM_ATT_IPACC_NS_LINK_CFG, 0, 8,
+		0x59, 0xd8, /* remote udp port (23000) */
+		192, 168, 100, 11, /* remote ip address */
+		0x59, 0xd8, /* local udp port (23000) */
+};
+
+static void patch_16(uint8_t *data, const uint16_t val)
+{
+	memcpy(data, &val, sizeof(val));
+}
+
+static void patch_32(uint8_t *data, const uint32_t val)
+{
+	memcpy(data, &val, sizeof(val));
+}
+
+/*
+ * Patch the various SYSTEM INFORMATION tables to update
+ * the LAI
+ */
+static void patch_nm_tables(struct gsm_bts *bts)
+{
+	u_int8_t arfcn_low = bts->c0->arfcn & 0xff;
+	u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+	/* patch ARFCN into BTS Attributes */
+	nanobts_attr_bts[42] &= 0xf0;
+	nanobts_attr_bts[42] |= arfcn_high;
+	nanobts_attr_bts[43] = arfcn_low;
+
+	/* patch the RACH attributes */
+	if (bts->rach_b_thresh != -1) {
+		nanobts_attr_bts[33] = bts->rach_b_thresh & 0xff;
+	}
+
+	if (bts->rach_ldavg_slots != -1) {
+		u_int8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+		nanobts_attr_bts[35] = avg_high;
+		nanobts_attr_bts[36] = avg_low;
+	}
+
+	/* patch BSIC */
+	nanobts_attr_bts[sizeof(nanobts_attr_bts)-11] = bts->bsic;
+
+	/* patch CGI */
+	abis_nm_ipaccess_cgi(nanobts_attr_bts+sizeof(nanobts_attr_bts)-7, bts);
+
+	/* patch the power reduction */
+	nanobts_attr_radio[1] = bts->c0->max_power_red / 2;
+
+	/* patch NSEI */
+	nanobts_attr_nse[3] = bts->gprs.nse.nsei >> 8;
+	nanobts_attr_nse[4] = bts->gprs.nse.nsei & 0xff;
+	memcpy(nanobts_attr_nse+8, bts->gprs.nse.timer,
+		ARRAY_SIZE(bts->gprs.nse.timer));
+	memcpy(nanobts_attr_nse+18, bts->gprs.cell.timer,
+		ARRAY_SIZE(bts->gprs.cell.timer));
+
+	/* patch NSVCI */
+	nanobts_attr_nsvc0[3] = bts->gprs.nsvc[0].nsvci >> 8;
+	nanobts_attr_nsvc0[4] = bts->gprs.nsvc[0].nsvci & 0xff;
+
+	/* patch IP address as SGSN IP */
+	patch_16(nanobts_attr_nsvc0 + 8, 
+			htons(bts->gprs.nsvc[0].remote_port));
+	patch_32(nanobts_attr_nsvc0 + 10,
+			htonl(bts->gprs.nsvc[0].remote_ip));
+	patch_16(nanobts_attr_nsvc0 + 14,
+			htons(bts->gprs.nsvc[0].local_port));
+
+	/* patch BVCI */
+	nanobts_attr_cell[12] = bts->gprs.cell.bvci >> 8;
+	nanobts_attr_cell[13] = bts->gprs.cell.bvci & 0xff;
+	/* patch RAC */
+	nanobts_attr_cell[3] = bts->gprs.rac;
+
+	if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+		/* patch EGPRS coding schemes MCS 1..9 */
+		nanobts_attr_cell[29] = 0x8f;
+		nanobts_attr_cell[30] = 0xff;
+	}
+}
+
+
+/* Callback function to be called whenever we get a GSM 12.21 state change event */
+static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
+{
+	u_int8_t obj_class = nsd->obj_class;
+	void *obj = nsd->obj;
+	struct gsm_nm_state *new_state = nsd->new_state;
+
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_bts_gprs_nsvc *nsvc;
+
+	/* This event-driven BTS setup is currently only required on nanoBTS */
+
+	/* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create
+	 * endless loop */
+	if (evt != S_NM_STATECHG_OPER)
+		return 0;
+
+	switch (obj_class) {
+	case NM_OC_SITE_MANAGER:
+		bts = container_of(obj, struct gsm_bts, site_mgr);
+		if ((new_state->operational == NM_OPSTATE_ENABLED &&
+		     new_state->availability == NM_AVSTATE_OK) ||
+		    (new_state->operational == NM_OPSTATE_DISABLED &&
+		     new_state->availability == NM_AVSTATE_OFF_LINE))
+			abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff);
+		break;
+	case NM_OC_BTS:
+		bts = obj;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			patch_nm_tables(bts);
+			abis_nm_set_bts_attr(bts, nanobts_attr_bts,
+					     sizeof(nanobts_attr_bts));
+			abis_nm_chg_adm_state(bts, obj_class,
+					      bts->bts_nr, 0xff, 0xff,
+					      NM_STATE_UNLOCKED);
+			abis_nm_opstart(bts, obj_class,
+					bts->bts_nr, 0xff, 0xff);
+		}
+		break;
+	case NM_OC_CHANNEL:
+		ts = obj;
+		trx = ts->trx;
+		if (new_state->operational == NM_OPSTATE_DISABLED &&
+		    new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			patch_nm_tables(trx->bts);
+			enum abis_nm_chan_comb ccomb =
+						abis_nm_chcomb4pchan(ts->pchan);
+			abis_nm_set_channel_attr(ts, ccomb);
+			abis_nm_chg_adm_state(trx->bts, obj_class,
+					      trx->bts->bts_nr, trx->nr, ts->nr,
+					      NM_STATE_UNLOCKED);
+			abis_nm_opstart(trx->bts, obj_class,
+					trx->bts->bts_nr, trx->nr, ts->nr);
+		}
+		break;
+	case NM_OC_RADIO_CARRIER:
+		trx = obj;
+		if (new_state->operational == NM_OPSTATE_DISABLED &&
+		    new_state->availability == NM_AVSTATE_OK)
+			abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr,
+					trx->nr, 0xff);
+		break;
+	case NM_OC_GPRS_NSE:
+		bts = container_of(obj, struct gsm_bts, gprs.nse);
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0xff, 0xff, nanobts_attr_nse,
+						  sizeof(nanobts_attr_nse));
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					0xff, 0xff);
+		}
+		break;
+	case NM_OC_GPRS_CELL:
+		bts = container_of(obj, struct gsm_bts, gprs.cell);
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0, 0xff, nanobts_attr_cell,
+						  sizeof(nanobts_attr_cell));
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					0, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      0, 0xff, NM_STATE_UNLOCKED);
+			abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr,
+					      0xff, 0xff, NM_STATE_UNLOCKED);
+		}
+		break;
+	case NM_OC_GPRS_NSVC:
+		nsvc = obj;
+		bts = nsvc->bts;
+		if (bts->gprs.mode == BTS_GPRS_NONE)
+			break;
+		/* We skip NSVC1 since we only use NSVC0 */
+		if (nsvc->id == 1)
+			break;
+		if (new_state->availability == NM_AVSTATE_OFF_LINE) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  nsvc->id, 0xff,
+						  nanobts_attr_nsvc0,
+						  sizeof(nanobts_attr_nsvc0));
+			abis_nm_opstart(bts, obj_class, bts->bts_nr,
+					nsvc->id, 0xff);
+			abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+					      nsvc->id, 0xff,
+					      NM_STATE_UNLOCKED);
+		}
+	default:
+		break;
+	}
+	return 0;
+}
+
+/* Callback function to be called every time we receive a 12.21 SW activated report */
+static int sw_activ_rep(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct gsm_bts *bts = mb->trx->bts;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+
+	if (!trx)
+		return -EINVAL;
+
+	if (trx->bts->type != GSM_BTS_TYPE_NANOBTS)
+		return 0;
+
+	switch (foh->obj_class) {
+	case NM_OC_BASEB_TRANSC:
+		abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+				      trx->bts->bts_nr, trx->nr, 0xff,
+				      NM_STATE_UNLOCKED);
+		abis_nm_opstart(trx->bts, foh->obj_class,
+				trx->bts->bts_nr, trx->nr, 0xff);
+		/* TRX software is active, tell it to initiate RSL Link */
+		abis_nm_ipaccess_rsl_connect(trx, 0, 3003, trx->rsl_tei);
+		break;
+	case NM_OC_RADIO_CARRIER: {
+		/*
+		 * Locking the radio carrier will make it go
+		 * offline again and we would come here. The
+		 * framework should determine that there was
+		 * no change and avoid recursion.
+		 *
+		 * This code is here to make sure that on start
+		 * a TRX remains locked.
+		 */
+		int rc_state = trx->nm_state.administrative;
+		/* Patch ARFCN into radio attribute */
+		nanobts_attr_radio[5] &= 0xf0;
+		nanobts_attr_radio[5] |= trx->arfcn >> 8;
+		nanobts_attr_radio[6] = trx->arfcn & 0xff;
+		abis_nm_set_radio_attr(trx, nanobts_attr_radio,
+				       sizeof(nanobts_attr_radio));
+		abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+				      trx->bts->bts_nr, trx->nr, 0xff,
+				      rc_state);
+		abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr,
+				trx->nr, 0xff);
+		break;
+		}
+	}
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+		     void *handler_data, void *signal_data)
+{
+	if (subsys != SS_NM)
+		return 0;
+
+	switch (signal) {
+	case S_NM_SW_ACTIV_REP:
+		return sw_activ_rep(signal_data);
+	case S_NM_STATECHG_OPER:
+	case S_NM_STATECHG_ADM:
+		return nm_statechg_event(signal, signal_data);
+	default:
+		break;
+	}
+	return 0;
+}
+
+int bts_model_nanobts_init(void)
+{
+	model_nanobts.features.data = &model_nanobts._features_data[0];
+	model_nanobts.features.data_len = sizeof(model_nanobts._features_data);
+
+	gsm_btsmodel_set_feature(&model_nanobts, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&model_nanobts, BTS_FEAT_EGPRS);
+
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_nanobts);
+}
diff --git a/src/libbsc/bts_siemens_bs11.c b/src/libbsc/bts_siemens_bs11.c
new file mode 100644
index 0000000..5a5f883
--- /dev/null
+++ b/src/libbsc/bts_siemens_bs11.c
@@ -0,0 +1,591 @@
+/* Siemens BS-11 specific code */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <osmocore/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+
+static struct gsm_bts_model model_bs11 = {
+	.type = GSM_BTS_TYPE_BS11,
+	.name = "bs11",
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.nm_att_tlvdef = {
+		.def = {
+			[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TLV },
+			/* BS11 specifics */
+			[NM_ATT_BS11_ESN_FW_CODE_NO] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_ESN_HW_CODE_NO] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_ESN_PCB_SERIAL] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_BOOT_SW_VERS] =	{ TLV_TYPE_TLV },
+			[0xd5] =			{ TLV_TYPE_TLV },
+			[0xa8] =			{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PASSWORD] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_TXPWR] =		{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_RSSI_OFFS] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LINE_CFG] = 	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_L1_PROT_TYPE] =	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_BIT_ERR_THESH] =	{ TLV_TYPE_FIXED, 2 },
+			[NM_ATT_BS11_DIVERSITY] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV },	
+			[NM_ATT_BS11_LMT_LOGIN_TIME] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV },
+			[NM_ATT_BS11_LMT_USER_NAME] =	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_BTS_STATE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_E1_STATE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PLL_MODE]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_PLL]	=	{ TLV_TYPE_TLV },
+			[NM_ATT_BS11_CCLK_ACCURACY] =	{ TLV_TYPE_TV },
+			[NM_ATT_BS11_CCLK_TYPE] =	{ TLV_TYPE_TV },
+			[0x95] =			{ TLV_TYPE_FIXED, 2 },
+		},
+	},
+};
+
+/* The following definitions are for OM and NM packets that we cannot yet
+ * generate by code but we just pass on */
+
+// BTS Site Manager, SET ATTRIBUTES
+
+/*
+  Object Class: BTS Site Manager
+  Instance 1: FF
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  sAbisExternalTime: 2007/09/08   14:36:11
+  omLAPDRelTimer: 30sec
+  shortLAPDIntTimer: 5sec
+  emergencyTimer1: 10 minutes
+  emergencyTimer2: 0 minutes
+*/
+
+unsigned char msg_1[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF,
+		NM_ATT_BS11_ABIS_EXT_TIME, 0x07,
+			0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE,
+		0x02,
+			0x00, 0x1E,
+		NM_ATT_BS11_SH_LAPD_INT_TIMER,
+			0x01, 0x05,
+		0x42, 0x02, 0x00, 0x0A,
+		0x44, 0x02, 0x00, 0x00
+};
+
+// BTS, SET BTS ATTRIBUTES
+
+/*
+  Object Class: BTS
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET BTS ATTRIBUTES
+  bsIdentityCode / BSIC:
+    PLMN_colour_code: 7h
+    BS_colour_code:   7h
+  BTS Air Timer T3105: 4  ,unit 10 ms
+  btsIsHopping: FALSE
+  periodCCCHLoadIndication: 1sec
+  thresholdCCCHLoadIndication: 0%
+  cellAllocationNumber: 00h = GSM 900
+  enableInterferenceClass: 00h =  Disabled
+  fACCHQual: 6 (FACCH stealing flags minus 1)
+  intaveParameter: 31 SACCH multiframes
+  interferenceLevelBoundaries:
+    Interference Boundary 1: 0Ah
+    Interference Boundary 2: 0Fh
+    Interference Boundary 3: 14h
+    Interference Boundary 4: 19h
+    Interference Boundary 5: 1Eh
+  mSTxPwrMax: 11
+      GSM range:     2=39dBm, 15=13dBm, stepsize 2 dBm
+      DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+      PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+                    30=33dBm, 31=32dBm
+  ny1:
+    Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20
+  powerOutputThresholds:
+    Out Power Fault Threshold:     -10 dB
+    Red Out Power Threshold:       - 6 dB
+    Excessive Out Power Threshold:   5 dB
+  rACHBusyThreshold: -127 dBm
+  rACHLoadAveragingSlots: 250 ,number of RACH burst periods
+  rfResourceIndicationPeriod: 125  SACCH multiframes
+  T200:
+    SDCCH:                044 in  5 ms
+    FACCH/Full rate:      031 in  5 ms
+    FACCH/Half rate:      041 in  5 ms
+    SACCH with TCH SAPI0: 090 in 10 ms
+    SACCH with SDCCH:     090 in 10 ms
+    SDCCH with SAPI3:     090 in  5 ms
+    SACCH with TCH SAPI3: 135 in 10 ms
+  tSync: 9000 units of 10 msec
+  tTrau: 9000 units of 10 msec
+  enableUmLoopTest: 00h =  disabled
+  enableExcessiveDistance: 00h =  Disabled
+  excessiveDistance: 64km
+  hoppingMode: 00h = baseband hopping
+  cellType: 00h =  Standard Cell
+  BCCH ARFCN / bCCHFrequency: 1
+*/
+
+static unsigned char bs11_attr_bts[] =
+{
+		NM_ATT_BSIC, HARDCODED_BSIC,
+		NM_ATT_BTS_AIR_TIMER, 0x04,
+		NM_ATT_BS11_BTSLS_HOPPING, 0x00,
+		NM_ATT_CCCH_L_I_P, 0x01,
+		NM_ATT_CCCH_L_T, 0x00,
+		NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM,
+		NM_ATT_BS11_ENA_INTERF_CLASS, 0x01,
+		NM_ATT_BS11_FACCH_QUAL, 0x06,
+		/* interference avg. period in numbers of SACCH multifr */
+		NM_ATT_INTAVE_PARAM, 0x1F,
+		NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B,
+		NM_ATT_CCCH_L_T, 0x23,
+		NM_ATT_GSM_TIME, 0x28, 0x00,
+		NM_ATT_ADM_STATE, 0x03,
+		NM_ATT_RACH_B_THRESH, 0x7F,
+		NM_ATT_LDAVG_SLOTS, 0x00, 0xFA,
+		NM_ATT_BS11_RF_RES_IND_PER, 0x7D,
+		NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87,
+		NM_ATT_BS11_TSYNC, 0x23, 0x28,
+		NM_ATT_BS11_TTRAU, 0x23, 0x28,
+		NM_ATT_TEST_DUR, 0x01, 0x00,
+		NM_ATT_OUTST_ALARM, 0x01, 0x00,
+		NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40,
+		NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00,
+		NM_ATT_BS11_PLL, 0x01, 0x00,
+		NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/,
+};
+
+// Handover Recognition, SET ATTRIBUTES
+
+/*
+Illegal Contents GSM Formatted O&M Msg
+  Object Class: Handover Recognition
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  enableDelayPowerBudgetHO: 00h = Disabled
+  enableDistanceHO: 00h =  Disabled
+  enableInternalInterCellHandover: 00h = Disabled
+  enableInternalIntraCellHandover: 00h =  Disabled
+  enablePowerBudgetHO: 00h = Disabled
+  enableRXLEVHO: 00h =  Disabled
+  enableRXQUALHO: 00h =  Disabled
+  hoAveragingDistance: 8  SACCH multiframes
+  hoAveragingLev:
+    A_LEV_HO: 8  SACCH multiframes
+    W_LEV_HO: 1  SACCH multiframes
+  hoAveragingPowerBudget:  16  SACCH multiframes
+  hoAveragingQual:
+    A_QUAL_HO: 8  SACCH multiframes
+    W_QUAL_HO: 2  SACCH multiframes
+  hoLowerThresholdLevDL: (10 - 110) dBm
+  hoLowerThresholdLevUL: (5 - 110) dBm
+  hoLowerThresholdQualDL: 06h =   6.4% < BER < 12.8%
+  hoLowerThresholdQualUL: 06h =   6.4% < BER < 12.8%
+  hoThresholdLevDLintra : (20 - 110) dBm
+  hoThresholdLevULintra: (20 - 110) dBm
+  hoThresholdMsRangeMax: 20 km
+  nCell: 06h
+  timerHORequest: 3  ,unit 2 SACCH multiframes
+*/
+
+unsigned char msg_3[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF,
+		0xD0, 0x00,		/* enableDelayPowerBudgetHO */
+		0x64, 0x00,		/* enableDistanceHO */
+		0x67, 0x00,		/* enableInternalInterCellHandover */
+		0x68, 0x00,		/* enableInternalInterCellHandover */
+		0x6A, 0x00,		/* enablePowerBudgetHO */
+		0x6C, 0x00,		/* enableRXLEVHO */
+		0x6D, 0x00,		/* enableRXQUALHO */
+		0x6F, 0x08,		/* hoAveragingDistance */
+		0x70, 0x08, 0x01,	/* hoAveragingLev */
+		0x71, 0x10, 0x10, 0x10,
+		0x72, 0x08, 0x02,	/* hoAveragingQual */
+		0x73, 0x0A,		/* hoLowerThresholdLevDL */
+		0x74, 0x05,		/* hoLowerThresholdLevUL */
+		0x75, 0x06,		/* hoLowerThresholdQualDL */
+		0x76, 0x06,		/* hoLowerThresholdQualUL */
+		0x78, 0x14,		/* hoThresholdLevDLintra */
+		0x79, 0x14,		/* hoThresholdLevULintra */
+		0x7A, 0x14,		/* hoThresholdMsRangeMax */
+		0x7D, 0x06,		/* nCell */
+		NM_ATT_BS11_TIMER_HO_REQUEST, 0x03,
+		0x20, 0x01, 0x00,
+		0x45, 0x01, 0x00,
+		0x48, 0x01, 0x00,
+		0x5A, 0x01, 0x00,
+		0x5B, 0x01, 0x05,
+		0x5E, 0x01, 0x1A,
+		0x5F, 0x01, 0x20,
+		0x9D, 0x01, 0x00,
+		0x47, 0x01, 0x00,
+		0x5C, 0x01, 0x64,
+		0x5D, 0x01, 0x1E,
+		0x97, 0x01, 0x20,
+		0xF7, 0x01, 0x3C,
+};
+
+// Power Control, SET ATTRIBUTES
+
+/*
+  Object Class: Power Control
+  BTS relat. Number: 0
+  Instance 2: FF
+  Instance 3: FF
+SET ATTRIBUTES
+  enableMsPowerControl: 00h =  Disabled
+  enablePowerControlRLFW: 00h =  Disabled
+  pcAveragingLev:
+    A_LEV_PC: 4  SACCH multiframes
+    W_LEV_PC: 1  SACCH multiframes
+  pcAveragingQual:
+    A_QUAL_PC: 4  SACCH multiframes
+    W_QUAL_PC: 2  SACCH multiframes
+  pcLowerThresholdLevDL: 0Fh
+  pcLowerThresholdLevUL: 0Ah
+  pcLowerThresholdQualDL: 05h =   3.2% < BER <  6.4%
+  pcLowerThresholdQualUL: 05h =   3.2% < BER <  6.4%
+  pcRLFThreshold: 0Ch
+  pcUpperThresholdLevDL: 14h
+  pcUpperThresholdLevUL: 0Fh
+  pcUpperThresholdQualDL: 04h =   1.6% < BER <  3.2%
+  pcUpperThresholdQualUL: 04h =   1.6% < BER <  3.2%
+  powerConfirm: 2  ,unit 2 SACCH multiframes
+  powerControlInterval: 2  ,unit 2 SACCH multiframes
+  powerIncrStepSize: 02h = 4 dB
+  powerRedStepSize: 01h = 2 dB
+  radioLinkTimeoutBs: 64  SACCH multiframes
+  enableBSPowerControl: 00h =  disabled
+*/
+
+unsigned char msg_4[] =
+{
+	NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF,
+		NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00,
+		NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00,
+		0x7E, 0x04, 0x01,	/* pcAveragingLev */
+		0x7F, 0x04, 0x02,	/* pcAveragingQual */
+		0x80, 0x0F,		/* pcLowerThresholdLevDL */
+		0x81, 0x0A,		/* pcLowerThresholdLevUL */
+		0x82, 0x05,		/* pcLowerThresholdQualDL */
+		0x83, 0x05,		/* pcLowerThresholdQualUL */
+		0x84, 0x0C, 		/* pcRLFThreshold */
+		0x85, 0x14, 		/* pcUpperThresholdLevDL */
+		0x86, 0x0F, 		/* pcUpperThresholdLevUL */
+		0x87, 0x04,		/* pcUpperThresholdQualDL */
+		0x88, 0x04,		/* pcUpperThresholdQualUL */
+		0x89, 0x02,		/* powerConfirm */
+		0x8A, 0x02,		/* powerConfirmInterval */
+		0x8B, 0x02,		/* powerIncrStepSize */
+		0x8C, 0x01,		/* powerRedStepSize */
+		0x8D, 0x40,		/* radioLinkTimeoutBs */
+		0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl
+};
+
+
+// Transceiver, SET TRX ATTRIBUTES (TRX 0)
+
+/*
+  Object Class: Transceiver
+  BTS relat. Number: 0
+  Tranceiver number: 0
+  Instance 3: FF
+SET TRX ATTRIBUTES
+  aRFCNList (HEX):  0001
+  txPwrMaxReduction: 00h =   30dB
+  radioMeasGran: 254  SACCH multiframes
+  radioMeasRep: 01h =  enabled
+  memberOfEmergencyConfig: 01h =  TRUE
+  trxArea: 00h = TRX doesn't belong to a concentric cell
+*/
+
+static unsigned char bs11_attr_radio[] =
+{
+		NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/,
+		NM_ATT_RF_MAXPOWR_R, 0x00,
+		NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05,
+		NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01,
+		NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01,
+		NM_ATT_BS11_TRX_AREA, 0x01, 0x00,
+};
+
+/*
+ * Patch the various SYSTEM INFORMATION tables to update
+ * the LAI
+ */
+static void patch_nm_tables(struct gsm_bts *bts)
+{
+	u_int8_t arfcn_low = bts->c0->arfcn & 0xff;
+	u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+	/* patch ARFCN into BTS Attributes */
+	bs11_attr_bts[69] &= 0xf0;
+	bs11_attr_bts[69] |= arfcn_high;
+	bs11_attr_bts[70] = arfcn_low;
+
+	/* patch ARFCN into TRX Attributes */
+	bs11_attr_radio[2] &= 0xf0;
+	bs11_attr_radio[2] |= arfcn_high;
+	bs11_attr_radio[3] = arfcn_low;
+
+	/* patch the RACH attributes */
+	if (bts->rach_b_thresh != -1)
+		bs11_attr_bts[33] = bts->rach_b_thresh & 0xff;
+
+	if (bts->rach_ldavg_slots != -1) {
+		u_int8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+		bs11_attr_bts[35] = avg_high;
+		bs11_attr_bts[36] = avg_low;
+	}
+
+	/* patch BSIC */
+	bs11_attr_bts[1] = bts->bsic;
+
+	/* patch the power reduction */
+	bs11_attr_radio[5] = bts->c0->max_power_red / 2;
+}
+
+
+static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+	enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan);
+	struct gsm_e1_subslot *e1l = &ts->e1_link;
+
+	abis_nm_set_channel_attr(ts, ccomb);
+
+	if (is_ipaccess_bts(ts->trx->bts))
+		return;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+		break;
+	default:
+		break;
+	}
+}
+
+static void nm_reconfig_trx(struct gsm_bts_trx *trx)
+{
+	struct gsm_e1_subslot *e1l = &trx->rsl_e1_link;
+	int i;
+
+	patch_nm_tables(trx->bts);
+
+	switch (trx->bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		/* FIXME: discover this by fetching an attribute */
+#if 0
+		trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */
+#else
+		trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */
+#endif
+		abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+		abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr,
+				      e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei);
+
+		/* Set Radio Attributes */
+		if (trx == trx->bts->c0)
+			abis_nm_set_radio_attr(trx, bs11_attr_radio,
+					       sizeof(bs11_attr_radio));
+		else {
+			u_int8_t trx1_attr_radio[sizeof(bs11_attr_radio)];
+			u_int8_t arfcn_low = trx->arfcn & 0xff;
+			u_int8_t arfcn_high = (trx->arfcn >> 8) & 0x0f;
+			memcpy(trx1_attr_radio, bs11_attr_radio,
+				sizeof(trx1_attr_radio));
+
+			/* patch ARFCN into TRX Attributes */
+			trx1_attr_radio[2] &= 0xf0;
+			trx1_attr_radio[2] |= arfcn_high;
+			trx1_attr_radio[3] = arfcn_low;
+
+			abis_nm_set_radio_attr(trx, trx1_attr_radio,
+					       sizeof(trx1_attr_radio));
+		}
+		break;
+	case GSM_BTS_TYPE_NANOBTS:
+		switch (trx->bts->band) {
+		case GSM_BAND_850:
+		case GSM_BAND_900:
+			trx->nominal_power = 20;
+			break;
+		case GSM_BAND_1800:
+		case GSM_BAND_1900:
+			trx->nominal_power = 23;
+			break;
+		default:
+			LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n",
+				gsm_band_name(trx->bts->band));
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		nm_reconfig_ts(&trx->ts[i]);
+}
+
+static void nm_reconfig_bts(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_BS11:
+		patch_nm_tables(bts);
+		abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/
+		abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts));
+		abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */
+		abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */
+		break;
+	default:
+		break;
+	}
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		nm_reconfig_trx(trx);
+}
+
+
+static void bootstrap_om_bs11(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+	/* stop sending event reports */
+	abis_nm_event_reports(bts, 0);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* reconfigure BTS with all TRX and all TS */
+	nm_reconfig_bts(bts);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	/* restart sending event reports */
+	abis_nm_event_reports(bts, 1);
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+	/* stop sending event reports */
+	abis_nm_event_reports(bts, 0);
+
+	/* begin DB transmission */
+	abis_nm_bs11_db_transmission(bts, 1);
+
+	/* end DB transmission */
+	abis_nm_bs11_db_transmission(bts, 0);
+
+	/* Reset BTS Site manager resource */
+	abis_nm_bs11_reset_resource(bts);
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_bts *bts;
+
+	if (subsys != SS_GLOBAL)
+		return 0;
+
+	switch (signal) {
+	case S_GLOBAL_BTS_CLOSE_OM:
+		bts = signal_data;
+		if (bts->type == GSM_BTS_TYPE_BS11)
+			shutdown_om(signal_data);
+		break;
+	}
+
+	return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct input_signal_data *isd = signal_data;
+
+	if (subsys != SS_INPUT)
+		return 0;
+
+	switch (signal) {
+	case S_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			if (isd->trx->bts->type == GSM_BTS_TYPE_BS11)
+				bootstrap_om_bs11(isd->trx->bts);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int bts_model_bs11_init(void)
+{
+	model_bs11.features.data = &model_bs11._features_data[0];
+	model_bs11.features.data_len = sizeof(model_bs11._features_data);
+
+	gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HSCSD);
+
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+	register_signal_handler(SS_GLOBAL, gbl_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_bs11);
+}
diff --git a/src/libbsc/bts_unknown.c b/src/libbsc/bts_unknown.c
new file mode 100644
index 0000000..f954599
--- /dev/null
+++ b/src/libbsc/bts_unknown.c
@@ -0,0 +1,41 @@
+/* Generic BTS - VTY code tries to allocate this BTS before type is known */
+
+/* (C) 2010 by Daniel Willmann <daniel@totalueberwachung.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/tlv.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_unknown = {
+	.type = GSM_BTS_TYPE_UNKNOWN,
+	.name = "unknown",
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.nm_att_tlvdef = {
+		.def = {
+		},
+	},
+};
+
+int bts_model_unknown_init(void)
+{
+	return gsm_bts_model_register(&model_unknown);
+}
diff --git a/src/libbsc/chan_alloc.c b/src/libbsc/chan_alloc.c
new file mode 100644
index 0000000..167381b
--- /dev/null
+++ b/src/libbsc/chan_alloc.c
@@ -0,0 +1,507 @@
+/* GSM Channel allocation routines
+ *
+ * (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+
+#include <osmocore/talloc.h>
+
+static int ts_is_usable(struct gsm_bts_trx_ts *ts)
+{
+	/* FIXME: How does this behave for BS-11 ? */
+	if (is_ipaccess_bts(ts->trx->bts)) {
+		if (!nm_is_running(&ts->nm_state))
+			return 0;
+	}
+
+	return 1;
+}
+
+int trx_is_usable(struct gsm_bts_trx *trx)
+{
+	/* FIXME: How does this behave for BS-11 ? */
+	if (is_ipaccess_bts(trx->bts)) {
+		if (!nm_is_running(&trx->nm_state) ||
+		    !nm_is_running(&trx->bb_transc.nm_state))
+			return 0;
+	}
+
+	return 1;
+}
+
+struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts,
+				   enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx *trx = bts->c0;
+	struct gsm_bts_trx_ts *ts = &trx->ts[0];
+
+	if (pchan != GSM_PCHAN_CCCH &&
+	    pchan != GSM_PCHAN_CCCH_SDCCH4)
+		return NULL;
+
+	if (ts->pchan != GSM_PCHAN_NONE)
+		return NULL;
+
+	ts->pchan = pchan;
+
+	return ts;
+}
+
+/* Allocate a physical channel (TS) */
+struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts,
+				enum gsm_phys_chan_config pchan)
+{
+	int j;
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int from, to;
+
+		if (!trx_is_usable(trx))
+			continue;
+
+		/* the following constraints are pure policy,
+		 * no requirement to put this restriction in place */
+		if (trx == bts->c0) {
+			/* On the first TRX we run one CCCH and one SDCCH8 */
+			switch (pchan) {
+			case GSM_PCHAN_CCCH:
+			case GSM_PCHAN_CCCH_SDCCH4:
+				from = 0; to = 0;
+				break;
+			case GSM_PCHAN_TCH_F:
+			case GSM_PCHAN_TCH_H:
+				from = 1; to = 7;
+				break;
+			case GSM_PCHAN_SDCCH8_SACCH8C:
+			default:
+				return NULL;
+			}
+		} else {
+			/* Every secondary TRX is configured for TCH/F
+			 * and TCH/H only */
+			switch (pchan) {
+			case GSM_PCHAN_SDCCH8_SACCH8C:
+				from = 1; to = 1;
+			case GSM_PCHAN_TCH_F:
+			case GSM_PCHAN_TCH_H:
+				from = 1; to = 7;
+				break;
+			default:
+				return NULL;
+			}
+		}
+
+		for (j = from; j <= to; j++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[j];
+
+			if (!ts_is_usable(ts))
+				continue;
+
+			if (ts->pchan == GSM_PCHAN_NONE) {
+				ts->pchan = pchan;
+				/* set channel attribute on OML */
+				abis_nm_set_channel_attr(ts, abis_nm_chcomb4pchan(pchan));
+				return ts;
+			}
+		}
+	}
+	return NULL;
+}
+
+/* Free a physical channel (TS) */
+void ts_free(struct gsm_bts_trx_ts *ts)
+{
+	ts->pchan = GSM_PCHAN_NONE;
+}
+
+static const u_int8_t subslots_per_pchan[] = {
+	[GSM_PCHAN_NONE] = 0,
+	[GSM_PCHAN_CCCH] = 0,
+	[GSM_PCHAN_CCCH_SDCCH4] = 4,
+	[GSM_PCHAN_TCH_F] = 1,
+	[GSM_PCHAN_TCH_H] = 2,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = 8,
+	/* FIXME: what about dynamic TCH_F_TCH_H ? */
+	[GSM_PCHAN_TCH_F_PDCH] = 1,
+};
+
+static struct gsm_lchan *
+_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx_ts *ts;
+	int j, ss;
+
+	if (!trx_is_usable(trx))
+		return NULL;
+
+	for (j = 0; j < 8; j++) {
+		ts = &trx->ts[j];
+		if (!ts_is_usable(ts))
+			continue;
+		/* ip.access dynamic TCH/F + PDCH combination */
+		if (ts->pchan == GSM_PCHAN_TCH_F_PDCH &&
+		    pchan == GSM_PCHAN_TCH_F) {
+			/* we can only consider such a dynamic channel
+			 * if the PDCH is currently inactive */
+			if (ts->flags & TS_F_PDCH_MODE)
+				continue;
+		} else if (ts->pchan != pchan)
+			continue;
+		/* check if all sub-slots are allocated yet */
+		for (ss = 0; ss < subslots_per_pchan[pchan]; ss++) {
+			struct gsm_lchan *lc = &ts->lchan[ss];
+			if (lc->type == GSM_LCHAN_NONE &&
+			    lc->state == LCHAN_S_NONE)
+				return lc;
+		}
+	}
+
+	return NULL;
+}
+
+static struct gsm_lchan *
+_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	struct gsm_lchan *lc;
+
+	if (bts->chan_alloc_reverse) {
+		llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan);
+			if (lc)
+				return lc;
+		}
+	} else {
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan);
+			if (lc)
+				return lc;
+		}
+	}
+
+	/* we cannot allocate more of these */
+	if (pchan == GSM_PCHAN_CCCH_SDCCH4)
+		return NULL;
+
+	/* if we've reached here, we need to allocate a new physical
+	 * channel for the logical channel type requested */
+	ts = ts_alloc(bts, pchan);
+	if (!ts) {
+		/* no more radio resources */
+		return NULL;
+	}
+	return &ts->lchan[0];
+}
+
+/* Allocate a logical channel */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type,
+			      int allow_bigger)
+{
+	struct gsm_lchan *lchan = NULL;
+	enum gsm_phys_chan_config first, second;
+
+	switch (type) {
+	case GSM_LCHAN_SDCCH:
+		if (bts->chan_alloc_reverse) {
+			first = GSM_PCHAN_SDCCH8_SACCH8C;
+			second = GSM_PCHAN_CCCH_SDCCH4;
+		} else {
+			first = GSM_PCHAN_CCCH_SDCCH4;
+			second = GSM_PCHAN_SDCCH8_SACCH8C;
+		}
+
+		lchan = _lc_find_bts(bts, first);
+		if (lchan == NULL)
+			lchan = _lc_find_bts(bts, second);
+
+		/* allow to assign bigger channels */
+		if (allow_bigger) {
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+				type = GSM_LCHAN_TCH_H;
+			}
+
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+				type = GSM_LCHAN_TCH_F;
+			}
+		}
+		break;
+	case GSM_LCHAN_TCH_F:
+		lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+		break;
+	case GSM_LCHAN_TCH_H:
+		lchan =_lc_find_bts(bts, GSM_PCHAN_TCH_H);
+		/* If we don't have TCH/H available, fall-back to TCH/F */
+		if (!lchan) {
+			lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+			type = GSM_LCHAN_TCH_F;
+		}
+		break;
+	default:
+		LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
+	}
+
+	if (lchan) {
+		lchan->type = type;
+
+		/* clear sapis */
+		memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis));
+
+		/* clear multi rate config */
+		memset(&lchan->mr_conf, 0, sizeof(lchan->mr_conf));
+	} else {
+		struct challoc_signal_data sig;
+		sig.bts = bts;
+		sig.type = type;
+		dispatch_signal(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig);
+	}
+
+	return lchan;
+}
+
+/* Free a logical channel */
+void lchan_free(struct gsm_lchan *lchan)
+{
+	struct challoc_signal_data sig;
+	int i;
+
+	sig.type = lchan->type;
+	lchan->type = GSM_LCHAN_NONE;
+
+
+	if (lchan->conn) {
+		struct lchan_signal_data sig;
+
+		/* We might kill an active channel... */
+		sig.lchan = lchan;
+		sig.mr = NULL;
+		dispatch_signal(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig);
+	}
+
+
+	/* stop the timer */
+	bsc_del_timer(&lchan->T3101);
+
+	/* clear cached measuement reports */
+	lchan->meas_rep_idx = 0;
+	for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) {
+		lchan->meas_rep[i].flags = 0;
+		lchan->meas_rep[i].nr = 0;
+	}
+	for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++)
+		lchan->neigh_meas[i].arfcn = 0;
+
+	if (lchan->rqd_ref) {
+		talloc_free(lchan->rqd_ref);
+		lchan->rqd_ref = NULL;
+		lchan->rqd_ta = 0;
+	}
+
+	sig.lchan = lchan;
+	sig.bts = lchan->ts->trx->bts;
+	dispatch_signal(SS_CHALLOC, S_CHALLOC_FREED, &sig);
+
+	if (lchan->conn) {
+		LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n");
+		lchan->conn = NULL;
+	}
+
+	lchan->sach_deact = 0;
+	lchan->release_reason = 0;
+
+	/* FIXME: ts_free() the timeslot, if we're the last logical
+	 * channel using it */
+}
+
+/*
+ * There was an error with the TRX and we need to forget
+ * any state so that a lchan can be allocated again after
+ * the trx is fully usable.
+ *
+ * This should be called after lchan_free to force a channel
+ * be available for allocation again. This means that this
+ * method will stop the "delay after error"-timer and set the
+ * state to LCHAN_S_NONE.
+ */
+void lchan_reset(struct gsm_lchan *lchan)
+{
+	bsc_del_timer(&lchan->T3101);
+	bsc_del_timer(&lchan->T3111);
+	bsc_del_timer(&lchan->error_timer);
+
+	lchan->type = GSM_LCHAN_NONE;
+	lchan->state = LCHAN_S_NONE;
+}
+
+/* release the next allocated SAPI or return 0 */
+static int _lchan_release_next_sapi(struct gsm_lchan *lchan)
+{
+	int sapi;
+
+	for (sapi = 1; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+		u_int8_t link_id;
+		if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+			continue;
+
+		link_id = sapi;
+		if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)
+			link_id |= 0x40;
+		rsl_release_request(lchan, link_id, lchan->release_reason);
+		return 0;
+	}
+
+	return 1;
+}
+
+/* Drive the release process of the lchan */
+static void _lchan_handle_release(struct gsm_lchan *lchan)
+{
+	/* Ask for SAPI != 0 to be freed first and stop if we need to wait */
+	if (_lchan_release_next_sapi(lchan) == 0)
+		return;
+
+	if (lchan->sach_deact) {
+		gsm48_send_rr_release(lchan);
+		return;
+	}
+
+	rsl_release_request(lchan, 0, lchan->release_reason);
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+}
+
+/* called from abis rsl */
+int rsl_lchan_rll_release(struct gsm_lchan *lchan, u_int8_t link_id)
+{
+	if (lchan->state != LCHAN_S_REL_REQ)
+		return -1;
+
+	if ((link_id & 0x7) != 0)
+		_lchan_handle_release(lchan);
+	return 0;
+}
+
+/* Consider releasing the channel now */
+int lchan_release(struct gsm_lchan *lchan, int sach_deact, int reason)
+{
+	DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+
+	lchan->conn = NULL;
+	lchan->release_reason = reason;
+	lchan->sach_deact = sach_deact;
+	_lchan_handle_release(lchan);
+	return 1;
+}
+
+static struct gsm_lchan* lchan_find(struct gsm_bts *bts, struct gsm_subscriber *subscr) {
+	struct gsm_bts_trx *trx;
+	int ts_no, lchan_no;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		for (ts_no = 0; ts_no < 8; ++ts_no) {
+			for (lchan_no = 0; lchan_no < TS_MAX_LCHAN; ++lchan_no) {
+				struct gsm_lchan *lchan =
+					&trx->ts[ts_no].lchan[lchan_no];
+				if (lchan->conn && subscr == lchan->conn->subscr)
+					return lchan;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr)
+{
+	struct gsm_bts *bts;
+	struct gsm_network *net = subscr->net;
+	struct gsm_lchan *lchan;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		lchan = lchan_find(bts, subscr);
+		if (lchan)
+			return lchan->conn;
+	}
+
+	return NULL;
+}
+
+void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int i;
+
+		/* skip administratively deactivated tranxsceivers */
+		if (!nm_is_running(&trx->nm_state) ||
+		    !nm_is_running(&trx->bb_transc.nm_state))
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+			struct load_counter *pl = &cl->pchan[ts->pchan];
+			int j;
+
+			/* skip administratively deactivated timeslots */
+			if (!nm_is_running(&ts->nm_state))
+				continue;
+
+			for (j = 0; j < subslots_per_pchan[ts->pchan]; j++) {
+				struct gsm_lchan *lchan = &ts->lchan[j];
+
+				pl->total++;
+
+				switch (lchan->state) {
+				case LCHAN_S_NONE:
+					break;
+				default:
+					pl->used++;
+					break;
+				}
+			}
+		}
+	}
+}
+
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net)
+{
+	struct gsm_bts *bts;
+
+	memset(pl, 0, sizeof(*pl));
+
+	llist_for_each_entry(bts, &net->bts_list, list)
+		bts_chan_load(pl, bts);
+}
+
diff --git a/src/libbsc/e1_config.c b/src/libbsc/e1_config.c
new file mode 100644
index 0000000..958839d
--- /dev/null
+++ b/src/libbsc/e1_config.c
@@ -0,0 +1,296 @@
+/* OpenBSC E1 Input code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/misdn.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+
+#define SAPI_L2ML	0
+#define SAPI_OML	62
+#define SAPI_RSL	0	/* 63 ? */
+
+/* The e1_reconfig_*() functions below tale the configuration present in the
+ * bts/trx/ts data structures and ensure the E1 configuration reflects the
+ * timeslot/subslot/TEI configuration */
+
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+	struct gsm_e1_subslot *e1_link = &ts->e1_link;
+	struct e1inp_line *line;
+	struct e1inp_ts *e1_ts;
+
+	DEBUGP(DMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	if (!e1_link->e1_ts) {
+		LOGP(DINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n",
+		     ts->nr, ts->trx->nr, ts->trx->bts->nr);
+		return 0;
+	}
+
+	line = e1inp_line_get(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DINP, LOGL_ERROR, "TS (%u/%u/%u) referring to "
+		     "non-existing E1 line %u\n", ts->nr, ts->trx->nr,
+		     ts->trx->bts->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		e1_ts = &line->ts[e1_link->e1_ts-1];
+		e1inp_ts_config(e1_ts, line, E1INP_TS_TYPE_TRAU);
+		subch_demux_activate(&e1_ts->trau.demux, e1_link->e1_ts_ss);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+int e1_reconfig_trx(struct gsm_bts_trx *trx)
+{
+	struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_line *line;
+	struct e1inp_sign_link *rsl_link;
+	int i;
+
+	if (!e1_link->e1_ts) {
+		LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link without "
+		     "timeslot?\n", trx->bts->nr, trx->nr);
+		return -EINVAL;
+	}
+
+	/* RSL Link */
+	line = e1inp_line_get(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring "
+		     "to non-existing E1 line %u\n", trx->bts->nr,
+		     trx->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN);
+	/* Ericsson RBS have a per-TRX OML link in parallel to RSL */
+	if (trx->bts->type == GSM_BTS_TYPE_RBS2000) {
+		struct e1inp_sign_link *oml_link;
+		oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx,
+						  trx->rsl_tei, SAPI_OML);
+		if (!oml_link) {
+			LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) OML link creation "
+				"failed\n", trx->bts->nr, trx->nr);
+			return -ENOMEM;
+		}
+		if (trx->oml_link)
+			e1inp_sign_link_destroy(trx->oml_link);
+		trx->oml_link = oml_link;
+	}
+	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, trx->rsl_tei, SAPI_RSL);
+	if (!rsl_link) {
+		LOGP(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation "
+		     "failed\n", trx->bts->nr, trx->nr);
+		return -ENOMEM;
+	}
+	if (trx->rsl_link)
+		e1inp_sign_link_destroy(trx->rsl_link);
+	trx->rsl_link = rsl_link;
+
+	for (i = 0; i < TRX_NR_TS; i++)
+		e1_reconfig_ts(&trx->ts[i]);
+
+	return 0;
+}
+
+int e1_reconfig_bts(struct gsm_bts *bts)
+{
+	struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_line *line;
+	struct e1inp_sign_link *oml_link;
+	struct gsm_bts_trx *trx;
+
+	DEBUGP(DMI, "e1_reconfig_bts(%u)\n", bts->nr);
+
+	if (!e1_link->e1_ts) {
+		LOGP(DINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n",
+		     bts->nr);
+		return -EINVAL;
+	}
+
+	/* OML link */
+	line = e1inp_line_get(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DINP, LOGL_ERROR, "BTS %u OML link referring to "
+		     "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN);
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, bts->oml_tei, SAPI_OML);
+	if (!oml_link) {
+		LOGP(DINP, LOGL_ERROR, "BTS %u OML link creation failed\n",
+		     bts->nr);
+		return -ENOMEM;
+	}
+	if (bts->oml_link)
+		e1inp_sign_link_destroy(bts->oml_link);
+	bts->oml_link = oml_link;
+
+	llist_for_each_entry(trx, &bts->trx_list, list)
+		e1_reconfig_trx(trx);
+
+	/* notify E1 input something has changed */
+	return e1inp_line_update(line);
+}
+
+#if 0
+/* do some compiled-in configuration for our BTS/E1 setup */
+int e1_config(struct gsm_bts *bts, int cardnr, int release_l2)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+	struct gsm_bts_trx *trx = bts->c0;
+	int base_ts;
+
+	switch (bts->nr) {
+	case 0:
+		/* First BTS uses E1 TS 01,02,03,04,05 */
+		base_ts = HARDCODED_BTS0_TS - 1;
+		break;
+	case 1:
+		/* Second BTS uses E1 TS 06,07,08,09,10 */
+		base_ts = HARDCODED_BTS1_TS - 1;
+		break;
+	case 2:
+		/* Third BTS uses E1 TS 11,12,13,14,15 */
+		base_ts = HARDCODED_BTS2_TS - 1;
+	default:
+		return -EINVAL;
+	}
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return -ENOMEM;
+
+	/* create E1 timeslots for signalling and TRAU frames */
+	e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN);
+	e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU);
+	e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[base_ts+1-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  trx, TEI_OML, SAPI_OML);
+	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, TEI_RSL, SAPI_RSL);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	trx->rsl_link = rsl_link;
+
+	/* enable subchannel demuxer on TS2 */
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1);
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2);
+	subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3);
+
+	/* enable subchannel demuxer on TS3 */
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2);
+	subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3);
+
+	trx = gsm_bts_trx_num(bts, 1);
+	if (trx) {
+		/* create E1 timeslots for TRAU frames of TRX1 */
+		e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU);
+		e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU);
+
+		/* create RSL signalling link for TRX1 */
+		sign_ts = &line->ts[base_ts+1-1];
+		rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+					  trx, TEI_RSL+1, SAPI_RSL);
+		/* create back-links from trx */
+		trx->rsl_link = rsl_link;
+
+		/* enable subchannel demuxer on TS2 */
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2);
+		subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3);
+
+		/* enable subchannel demuxer on TS3 */
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2);
+		subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3);
+	}
+
+	return mi_setup(cardnr, line, release_l2);
+}
+#endif
+
+/* configure pseudo E1 line in ip.access style and connect to BTS */
+int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts, *rsl_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+
+	line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+	if (!line)
+		return -ENOMEM;
+
+	/* create E1 timeslots for signalling and TRAU frames */
+	e1inp_ts_config(&line->ts[1-1], line, E1INP_TS_TYPE_SIGN);
+	e1inp_ts_config(&line->ts[2-1], line, E1INP_TS_TYPE_SIGN);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[1-1];
+	rsl_ts = &line->ts[2-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, 0xff, 0);
+	rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL,
+					  bts->c0, 0, 0);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	bts->c0->rsl_link = rsl_link;
+
+	/* default port at BTS for incoming connections is 3006 */
+	if (sin->sin_port == 0)
+		sin->sin_port = htons(3006);
+
+	return ipaccess_connect(line, sin);
+}
diff --git a/src/libbsc/gsm_04_08_utils.c b/src/libbsc/gsm_04_08_utils.c
new file mode 100644
index 0000000..6d12cc0
--- /dev/null
+++ b/src/libbsc/gsm_04_08_utils.c
@@ -0,0 +1,655 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0
+ * utility functions
+ */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/gsm48.h>
+
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+
+/* should ip.access BTS use direct RTP streams between each other (1),
+ * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
+int ipacc_rtp_direct = 1;
+
+static int gsm48_sendmsg(struct msgb *msg)
+{
+	if (msg->lchan)
+		msg->trx = msg->lchan->ts->trx;
+
+	msg->l3h = msg->data;
+	return rsl_data_request(msg, 0);
+}
+
+/* Section 9.1.8 / Table 9.9 */
+struct chreq {
+	u_int8_t val;
+	u_int8_t mask;
+	enum chreq_type type;
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */
+static const struct chreq chreq_type_neci1[] = {
+	{ 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+	{ 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F },
+	{ 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H },
+	{ 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL },
+	{ 0xe0, 0xe0, CHREQ_T_TCH_F },
+	{ 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H },
+	{ 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+	{ 0x00, 0xf0, CHREQ_T_LOCATION_UPD },
+	{ 0x10, 0xf0, CHREQ_T_SDCCH },
+	{ 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 },
+	{ 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+	{ 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+	{ 0x67, 0xff, CHREQ_T_LMU },
+	{ 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+	{ 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+	{ 0x63,	0xff, CHREQ_T_RESERVED_SDCCH },
+	{ 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */
+static const struct chreq chreq_type_neci0[] = {
+	{ 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+	{ 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H },
+	{ 0xe0, 0xe0, CHREQ_T_TCH_F },
+	{ 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+	{ 0x00, 0xe0, CHREQ_T_LOCATION_UPD },
+	{ 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 },
+	{ 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+	{ 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+	{ 0x67, 0xff, CHREQ_T_LMU },
+	{ 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+	{ 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+	{ 0x63,	0xff, CHREQ_T_RESERVED_SDCCH },
+	{ 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+static const enum gsm_chan_t ctype_by_chreq[] = {
+	[CHREQ_T_EMERG_CALL]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_CALL_REEST_TCH_F]	= GSM_LCHAN_TCH_F,
+	[CHREQ_T_CALL_REEST_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_CALL_REEST_TCH_H_DBL]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_SDCCH]			= GSM_LCHAN_SDCCH,
+	[CHREQ_T_TCH_F]			= GSM_LCHAN_TCH_F,
+	[CHREQ_T_VOICE_CALL_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_DATA_CALL_TCH_H]	= GSM_LCHAN_TCH_H,
+	[CHREQ_T_LOCATION_UPD]		= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_ANY_NECI1]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_ANY_NECI0]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_PAG_R_TCH_F]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_PAG_R_TCH_FH]		= GSM_LCHAN_TCH_F,
+	[CHREQ_T_LMU]			= GSM_LCHAN_SDCCH,
+	[CHREQ_T_RESERVED_SDCCH]	= GSM_LCHAN_SDCCH,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_LCHAN_UNKNOWN,
+};
+
+static const enum gsm_chreq_reason_t reason_by_chreq[] = {
+	[CHREQ_T_EMERG_CALL]		= GSM_CHREQ_REASON_EMERG,
+	[CHREQ_T_CALL_REEST_TCH_F]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_CALL_REEST_TCH_H]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_CALL_REEST_TCH_H_DBL]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_SDCCH]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_TCH_F]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_VOICE_CALL_TCH_H]	= GSM_CHREQ_REASON_CALL,
+	[CHREQ_T_DATA_CALL_TCH_H]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_LOCATION_UPD]		= GSM_CHREQ_REASON_LOCATION_UPD,
+	[CHREQ_T_PAG_R_ANY_NECI1]	= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_ANY_NECI0]	= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_TCH_F]		= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_PAG_R_TCH_FH]		= GSM_CHREQ_REASON_PAG,
+	[CHREQ_T_LMU]			= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_RESERVED_SDCCH]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_CHREQ_REASON_OTHER,
+};
+
+/* verify that the two tables match */
+static_assert(sizeof(ctype_by_chreq) ==
+	      sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size);
+
+/*
+ * Update channel types for request based on policy. E.g. in the
+ * case of a TCH/H network/bsc use TCH/H for the emergency calls,
+ * for early assignment assign a SDCCH and some other options.
+ */
+void gsm_net_update_ctype(struct gsm_network *network)
+{
+	/* copy over the data */
+	memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq));
+
+	/*
+	 * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it
+	 * is better to iterate over the BTS/TRX and check if no TCH/F is available
+	 * and then set it to TCH/H.
+	 */
+	if (network->neci)
+		network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H;
+
+	if (network->pag_any_tch) {
+		if (network->neci) {
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H;
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H;
+		} else {
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F;
+			network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F;
+		}
+	}
+}
+
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, u_int8_t ra)
+{
+	int i;
+	int length;
+	const struct chreq *chreq;
+
+	if (network->neci) {
+		chreq = chreq_type_neci1;
+		length = ARRAY_SIZE(chreq_type_neci1);
+	} else {
+		chreq = chreq_type_neci0;
+		length = ARRAY_SIZE(chreq_type_neci0);
+	}
+
+
+	for (i = 0; i < length; i++) {
+		const struct chreq *chr = &chreq[i];
+		if ((ra & chr->mask) == chr->val)
+			return network->ctype_by_chreq[chr->type];
+	}
+	LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra);
+	return GSM_LCHAN_SDCCH;
+}
+
+enum gsm_chreq_reason_t get_reason_by_chreq(u_int8_t ra, int neci)
+{
+	int i;
+	int length;
+	const struct chreq *chreq;
+
+	if (neci) {
+		chreq = chreq_type_neci1;
+		length = ARRAY_SIZE(chreq_type_neci1);
+	} else {
+		chreq = chreq_type_neci0;
+		length = ARRAY_SIZE(chreq_type_neci0);
+	}
+
+	for (i = 0; i < length; i++) {
+		const struct chreq *chr = &chreq[i];
+		if ((ra & chr->mask) == chr->val)
+			return reason_by_chreq[chr->type];
+	}
+	LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra);
+	return GSM_CHREQ_REASON_OTHER;
+}
+
+/* 7.1.7 and 9.1.7: RR CHANnel RELease */
+int gsm48_send_rr_release(struct gsm_lchan *lchan)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	u_int8_t *cause;
+
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CHAN_REL;
+
+	cause = msgb_put(msg, 1);
+	cause[0] = GSM48_RR_CAUSE_NORMAL;
+
+	DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n",
+		lchan->nr, lchan->type);
+
+	/* Send actual release request to MS */
+	gsm48_sendmsg(msg);
+	/* FIXME: Start Timer T3109 */
+
+	/* Deactivate the SACCH on the BTS side */
+	return rsl_deact_sacch(lchan);
+}
+
+int send_siemens_mrpci(struct gsm_lchan *lchan,
+		       u_int8_t *classmark2_lv)
+{
+	struct rsl_mrpci mrpci;
+
+	if (classmark2_lv[0] < 2)
+		return -EINVAL;
+
+	mrpci.power_class = classmark2_lv[1] & 0x7;
+	mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1);
+	mrpci.vbs_capable = classmark2_lv[2] & (1 <<2);
+	mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3;
+
+	return rsl_siemens_mrpci(lchan, &mrpci);
+}
+
+int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type)
+{
+	/* Check the size for the classmark */
+	if (length < 1 + *classmark2_lv)
+		return -1;
+
+	u_int8_t *mi_lv = classmark2_lv + *classmark2_lv + 1;
+	if (length < 2 + *classmark2_lv + mi_lv[0])
+		return -2;
+
+	*mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
+	return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv);
+}
+
+int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length,
+			    char *mi_string, u_int8_t *mi_type)
+{
+	static const uint32_t classmark_offset =
+		offsetof(struct gsm48_pag_resp, classmark2);
+	u_int8_t *classmark2_lv = (uint8_t *) &resp->classmark2;
+	return gsm48_extract_mi(classmark2_lv, length - classmark_offset,
+				mi_string, mi_type);
+}
+
+int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg, struct gsm_subscriber *subscr)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t *classmark2_lv = gh->data + 1;
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2_lv);
+
+	if (!conn->subscr) {
+		conn->subscr = subscr;
+	} else if (conn->subscr != subscr) {
+		LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n");
+		subscr_put(subscr);
+		return -EINVAL;
+	} else {
+		DEBUGP(DRR, "<- Channel already owned by us\n");
+		subscr_put(subscr);
+		subscr = conn->subscr;
+	}
+
+	counter_inc(bts->network->stats.paging.completed);
+
+	/* Stop paging on the bts we received the paging response */
+	paging_request_stop(conn->bts, subscr, conn, msg);
+	return 0;
+}
+
+/* Chapter 9.1.9: Ciphering Mode Command */
+int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	u_int8_t ciph_mod_set;
+
+	msg->lchan = lchan;
+
+	DEBUGP(DRR, "TX CIPHERING MODE CMD\n");
+
+	if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0))
+		ciph_mod_set = 0;
+	else
+		ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CIPH_M_CMD;
+	gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf);
+
+	return rsl_encryption_cmd(msg);
+}
+
+static void gsm48_cell_desc(struct gsm48_cell_desc *cd,
+			    const struct gsm_bts *bts)
+{
+	cd->ncc = (bts->bsic >> 3 & 0x7);
+	cd->bcc = (bts->bsic & 0x7);
+	cd->arfcn_hi = bts->c0->arfcn >> 8;
+	cd->arfcn_lo = bts->c0->arfcn & 0xff;
+}
+
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+			   const struct gsm_lchan *lchan)
+{
+	u_int16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+
+	cd->chan_nr = lchan2chan_nr(lchan);
+	if (!lchan->ts->hopping.enabled) {
+		cd->h0.tsc = lchan->ts->trx->bts->tsc;
+		cd->h0.h = 0;
+		cd->h0.arfcn_high = arfcn >> 8;
+		cd->h0.arfcn_low = arfcn & 0xff;
+	} else {
+		cd->h1.tsc = lchan->ts->trx->bts->tsc;
+		cd->h1.h = 1;
+		cd->h1.maio_high = lchan->ts->hopping.maio >> 2;
+		cd->h1.maio_low = lchan->ts->hopping.maio & 0x03;
+		cd->h1.hsn = lchan->ts->hopping.hsn;
+	}
+}
+
+#define GSM48_HOCMD_CCHDESC_LEN	16
+
+/* Chapter 9.1.15: Handover Command */
+int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
+		      u_int8_t power_command, u_int8_t ho_ref)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_ho_cmd *ho =
+		(struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho));
+
+	msg->lchan = old_lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_HANDO_CMD;
+
+	/* mandatory bits */
+	gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts);
+	gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan);
+	ho->ho_ref = ho_ref;
+	ho->power_command = power_command;
+
+	if (new_lchan->ts->hopping.enabled) {
+		struct gsm_bts *bts = new_lchan->ts->trx->bts;
+		struct gsm48_system_information_type_1 *si1;
+		uint8_t *cur;
+
+		si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1);
+		/* Copy the Cell Chan Desc (ARFCNS in this cell) */
+		msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC);
+		cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN);
+		memcpy(cur, si1->cell_channel_description,
+			GSM48_HOCMD_CCHDESC_LEN);
+		/* Copy the Mobile Allocation */
+		msgb_tlv_put(msg, GSM48_IE_MA_BEFORE,
+			     new_lchan->ts->hopping.ma_len,
+			     new_lchan->ts->hopping.ma_data);
+	}
+	/* FIXME: optional bits for type of synchronization? */
+
+	return gsm48_sendmsg(msg);
+}
+
+/* Chapter 9.1.2: Assignment Command */
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, u_int8_t power_command)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_ass_cmd *ass =
+		(struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass));
+
+	DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode);
+
+	msg->lchan = dest_lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_ASS_CMD;
+
+	/*
+	 * fill the channel information element, this code
+	 * should probably be shared with rsl_rx_chan_rqd(),
+	 * gsm48_tx_chan_mode_modify. But beware that 10.5.2.5
+	 * 10.5.2.5.a have slightly different semantic for
+	 * the chan_desc. But as long as multi-slot configurations
+	 * are not used we seem to be fine.
+	 */
+	gsm48_lchan2chan_desc(&ass->chan_desc, lchan);
+	ass->power_command = power_command;
+
+	/* optional: cell channel description */
+
+	msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode);
+
+	/* mobile allocation in case of hopping */
+	if (lchan->ts->hopping.enabled) {
+		msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len,
+			     lchan->ts->hopping.ma_data);
+	}
+
+	/* in case of multi rate we need to attach a config */
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		if (lchan->mr_conf.ver == 0) {
+			LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec "
+				"without multirate config.\n");
+		} else {
+			u_int8_t *data = msgb_put(msg, 4);
+			data[0] = GSM48_IE_MUL_RATE_CFG;
+			data[1] = 0x2;
+			memcpy(&data[2], &lchan->mr_conf, 2);
+		}
+	}
+
+	return gsm48_sendmsg(msg);
+}
+
+/* 9.1.5 Channel mode modify: Modify the mode on the MS side */
+int gsm48_tx_chan_mode_modify(struct gsm_lchan *lchan, u_int8_t mode)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_chan_mode_modify *cmm =
+		(struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm));
+
+	DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode);
+
+	lchan->tch_mode = mode;
+	msg->lchan = lchan;
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF;
+
+	/* fill the channel information element, this code
+	 * should probably be shared with rsl_rx_chan_rqd() */
+	gsm48_lchan2chan_desc(&cmm->chan_desc, lchan);
+	cmm->mode = mode;
+
+	/* in case of multi rate we need to attach a config */
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		if (lchan->mr_conf.ver == 0) {
+			LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec "
+				"without multirate config.\n");
+		} else {
+			u_int8_t *data = msgb_put(msg, 4);
+			data[0] = GSM48_IE_MUL_RATE_CFG;
+			data[1] = 0x2;
+			memcpy(&data[2], &lchan->mr_conf, 2);
+		}
+	}
+
+	return gsm48_sendmsg(msg);
+}
+
+int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode)
+{
+	int rc;
+
+	rc = gsm48_tx_chan_mode_modify(lchan, lchan_mode);
+	if (rc < 0)
+		return rc;
+
+	return rc;
+}
+
+int gsm48_rx_rr_modif_ack(struct msgb *msg)
+{
+	int rc;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_chan_mode_modify *mod =
+				(struct gsm48_chan_mode_modify *) gh->data;
+
+	DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n");
+
+	if (mod->mode != msg->lchan->tch_mode) {
+		LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n",
+			msg->lchan->tch_mode, mod->mode);
+		return -1;
+	}
+
+	/* update the channel type */
+	switch (mod->mode) {
+	case GSM48_CMODE_SIGN:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+		break;
+	case GSM48_CMODE_SPEECH_V1:
+	case GSM48_CMODE_SPEECH_EFR:
+	case GSM48_CMODE_SPEECH_AMR:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+		break;
+	case GSM48_CMODE_DATA_14k5:
+	case GSM48_CMODE_DATA_12k0:
+	case GSM48_CMODE_DATA_6k0:
+	case GSM48_CMODE_DATA_3k6:
+		msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA;
+		break;
+	}
+
+	/* We've successfully modified the MS side of the channel,
+	 * now go on to modify the BTS side of the channel */
+	rc = rsl_chan_mode_modify_req(msg->lchan);
+
+	/* FIXME: we not only need to do this after mode modify, but
+	 * also after channel activation */
+	if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN)
+		rsl_ipacc_crcx(msg->lchan);
+	return rc;
+}
+
+int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t *data = gh->data;
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct bitvec *nbv = &bts->si_common.neigh_list;
+	struct gsm_meas_rep_cell *mrc;
+
+	if (gh->msg_type != GSM48_MT_RR_MEAS_REP)
+		return -EINVAL;
+
+	if (data[0] & 0x80)
+		rep->flags |= MEAS_REP_F_BA1;
+	if (data[0] & 0x40)
+		rep->flags |= MEAS_REP_F_UL_DTX;
+	if ((data[1] & 0x40) == 0x00)
+		rep->flags |= MEAS_REP_F_DL_VALID;
+
+	rep->dl.full.rx_lev = data[0] & 0x3f;
+	rep->dl.sub.rx_lev = data[1] & 0x3f;
+	rep->dl.full.rx_qual = (data[3] >> 4) & 0x7;
+	rep->dl.sub.rx_qual = (data[3] >> 1) & 0x7;
+
+	rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2);
+	if (rep->num_cell < 1 || rep->num_cell > 6)
+		return 0;
+
+	/* an encoding nightmare in perfection */
+	mrc = &rep->cell[0];
+	mrc->rxlev = data[3] & 0x3f;
+	mrc->neigh_idx = data[4] >> 3;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5);
+	if (rep->num_cell < 2)
+		return 0;
+
+	mrc = &rep->cell[1];
+	mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7);
+	mrc->neigh_idx = (data[6] >> 2) & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4);
+	if (rep->num_cell < 3)
+		return 0;
+
+	mrc = &rep->cell[2];
+	mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6);
+	mrc->neigh_idx = (data[8] >> 1) & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3);
+	if (rep->num_cell < 4)
+		return 0;
+
+	mrc = &rep->cell[3];
+	mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5);
+	mrc->neigh_idx = data[10] & 0x1f;
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = data[11] >> 2;
+	if (rep->num_cell < 5)
+		return 0;
+
+	mrc = &rep->cell[4];
+	mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4);
+	mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7);
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = (data[13] >> 1) & 0x3f;
+	if (rep->num_cell < 6)
+		return 0;
+
+	mrc = &rep->cell[5];
+	mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3);
+	mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6);
+	mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+	mrc->bsic = data[15] & 0x3f;
+
+	return 0;
+}
+
+struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value)
+{
+	struct msgb *msg;
+	struct gsm48_hdr *gh;
+
+	msg = gsm48_msgb_alloc();
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_CM_SERV_REJ;
+	gh->data[0] = value;
+
+	return msg;
+}
+
+struct msgb *gsm48_create_loc_upd_rej(uint8_t cause)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm48_msgb_alloc();
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT;
+	gh->data[0] = cause;
+	return msg;
+}
diff --git a/src/libbsc/gsm_subscriber_base.c b/src/libbsc/gsm_subscriber_base.c
new file mode 100644
index 0000000..caf84e7
--- /dev/null
+++ b/src/libbsc/gsm_subscriber_base.c
@@ -0,0 +1,149 @@
+/* The concept of a subscriber as seen by the BSC */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+LLIST_HEAD(active_subscribers);
+void *tall_subscr_ctx;
+
+/* for the gsm_subscriber.c */
+struct llist_head *subscr_bsc_active_subscriber(void)
+{
+	return &active_subscribers;
+}
+
+
+char *subscr_name(struct gsm_subscriber *subscr)
+{
+	if (strlen(subscr->name))
+		return subscr->name;
+
+	return subscr->imsi;
+}
+
+struct gsm_subscriber *subscr_alloc(void)
+{
+	struct gsm_subscriber *s;
+
+	s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber);
+	if (!s)
+		return NULL;
+
+	llist_add_tail(&s->entry, &active_subscribers);
+	s->use_count = 1;
+	s->tmsi = GSM_RESERVED_TMSI;
+
+	INIT_LLIST_HEAD(&s->requests);
+
+	return s;
+}
+
+static void subscr_free(struct gsm_subscriber *subscr)
+{
+	llist_del(&subscr->entry);
+	talloc_free(subscr);
+}
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
+{
+	subscr->use_count++;
+	DEBUGP(DREF, "subscr %s usage increases usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
+{
+	subscr->use_count--;
+	DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	if (subscr->use_count <= 0 && !subscr->net->keep_subscr)
+		subscr_free(subscr);
+	return NULL;
+}
+
+struct gsm_subscriber *subscr_get_or_create(struct gsm_network *net,
+					    const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net)
+			return subscr_get(subscr);
+	}
+
+	subscr = subscr_alloc();
+	if (!subscr)
+		return NULL;
+
+	strcpy(subscr->imsi, imsi);
+	subscr->net = net;
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_network *net, uint32_t tmsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->tmsi == tmsi && subscr->net == net)
+			return subscr_get(subscr);
+	}
+
+	return NULL;
+}
+
+struct gsm_subscriber *subscr_active_by_imsi(struct gsm_network *net, const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net)
+			return subscr_get(subscr);
+	}
+
+	return NULL;
+}
+
+int subscr_purge_inactive(struct gsm_network *net)
+{
+	struct gsm_subscriber *subscr, *tmp;
+	int purged = 0;
+
+	llist_for_each_entry_safe(subscr, tmp, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->net == net && subscr->use_count <= 0) {
+			subscr_free(subscr);
+			purged += 1;
+		}
+	}
+
+	return purged;
+}
diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c
new file mode 100644
index 0000000..d3f843a
--- /dev/null
+++ b/src/libbsc/handover_decision.c
@@ -0,0 +1,297 @@
+/* Handover Decision making for Inter-BTS (Intra-BSC) Handover.  This
+ * only implements the handover algorithm/decision, but not execution
+ * of it */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+#include <openbsc/handover.h>
+#include <osmocore/gsm_utils.h>
+
+/* issue handover to a cell identified by ARFCN and BSIC */
+static int handover_to_arfcn_bsic(struct gsm_lchan *lchan,
+				  u_int16_t arfcn, u_int8_t bsic)
+{
+	struct gsm_bts *new_bts;
+
+	/* resolve the gsm_bts structure for the best neighbor */
+	new_bts = gsm_bts_neighbor(lchan->ts->trx->bts, arfcn, bsic);
+	if (!new_bts) {
+		LOGP(DHO, LOGL_NOTICE, "unable to determine neighbor BTS "
+		     "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic);
+		return -EINVAL;
+	}
+
+	/* and actually try to handover to that cell */
+	return bsc_handover_start(lchan, new_bts);
+}
+
+/* did we get a RXLEV for a given cell in the given report? */
+static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr,
+				 u_int16_t arfcn, u_int8_t bsic)
+{
+	int i;
+
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+
+		/* search for matching report */
+		if (!(mrc->arfcn == arfcn && mrc->bsic == bsic))
+			continue;
+
+		mrc->flags |= MRC_F_PROCESSED;
+		return mrc->rxlev;
+	}
+	return -ENODEV;
+}
+
+/* obtain averaged rxlev for given neighbor */
+static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window)
+{
+	unsigned int i, idx;
+	int avg = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev),
+				nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev),
+				window);
+
+	for (i = 0; i < window; i++) {
+		int j = (idx+i) % ARRAY_SIZE(nmp->rxlev);
+
+		avg += nmp->rxlev[j];
+	}
+
+	return avg / window;
+}
+
+/* find empty or evict bad neighbor */
+static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan)
+{
+	int j, worst = 999999;
+	struct neigh_meas_proc *nmp_worst;
+
+	/* first try to find an empty/unused slot */
+	for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+		if (!nmp->arfcn)
+			return nmp;
+	}
+
+	/* no empty slot found. evict worst neighbor from list */
+	for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+		int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG);
+		if (avg < worst) {
+			worst = avg;
+			nmp_worst = nmp;
+		}
+	}
+
+	return nmp_worst;
+}
+
+/* process neighbor cell measurement reports */
+static void process_meas_neigh(struct gsm_meas_rep *mr)
+{
+	int i, j, idx;
+
+	/* for each reported cell, try to update global state */
+	for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) {
+		struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j];
+		unsigned int idx;
+		int rxlev;
+
+		/* skip unused entries */
+		if (!nmp->arfcn)
+			continue;
+
+		rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic);
+		idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+		if (rxlev >= 0) {
+			nmp->rxlev[idx] = rxlev;
+			nmp->last_seen_nr = mr->nr;
+		} else
+			nmp->rxlev[idx] = 0;
+		nmp->rxlev_cnt++;
+	}
+
+	/* iterate over list of reported cells, check if we did not
+	 * process all of them */
+	for (i = 0; i < mr->num_cell; i++) {
+		struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+		struct neigh_meas_proc *nmp;
+
+		if (mrc->flags & MRC_F_PROCESSED)
+			continue;
+
+		nmp = find_evict_neigh(mr->lchan);
+
+		nmp->arfcn = mrc->arfcn;
+		nmp->bsic = mrc->bsic;
+
+		idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+		nmp->rxlev[idx] = mrc->rxlev;
+		nmp->rxlev_cnt++;
+		nmp->last_seen_nr = mr->nr;
+
+		mrc->flags |= MRC_F_PROCESSED;
+	}
+}
+
+/* attempt to do a handover */
+static int attempt_handover(struct gsm_meas_rep *mr)
+{
+	struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+	struct neigh_meas_proc *best_cell = NULL;
+	unsigned int best_better_db = 0;
+	int i, rc;
+
+	/* find the best cell in this report that is at least RXLEV_HYST
+	 * better than the current serving cell */
+
+	for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) {
+		struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i];
+		int avg, better;
+
+		/* skip empty slots */
+		if (nmp->arfcn == 0)
+			continue;
+
+		/* caculate average rxlev for this cell over the window */
+		avg = neigh_meas_avg(nmp, net->handover.win_rxlev_avg_neigh);
+
+		/* check if hysteresis is fulfilled */
+		if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis)
+			continue;
+
+		better = avg - mr->dl.full.rx_lev;
+		if (better > best_better_db) {
+			best_cell = nmp;
+			best_better_db = better;
+		}
+	}
+
+	if (!best_cell)
+		return 0;
+
+	LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ",
+		gsm_ts_name(mr->lchan->ts), best_cell->arfcn);
+	if (!net->handover.active) {
+		LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n");
+		return 0;
+	}
+
+	rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic);
+	switch (rc) {
+	case 0:
+		LOGPC(DHO, LOGL_INFO, "Starting handover\n");
+		break;
+	case -ENOSPC:
+		LOGPC(DHO, LOGL_INFO, "No channel available\n");
+		break;
+	case -EBUSY:
+		LOGPC(DHO, LOGL_INFO, "Handover already active\n");
+		break;
+	default:
+		LOGPC(DHO, LOGL_ERROR, "Unknown error\n");
+	}
+	return rc;
+}
+
+/* process an already parsed measurement report and decide if we want to
+ * attempt a handover */
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+	struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+	int av_rxlev;
+
+	/* we currently only do handover for TCH channels */
+	switch (mr->lchan->type) {
+	case GSM_LCHAN_TCH_F:
+	case GSM_LCHAN_TCH_H:
+		break;
+	default:
+		return 0;
+	}
+
+	/* parse actual neighbor cell info */
+	if (mr->num_cell > 0 && mr->num_cell < 7)
+		process_meas_neigh(mr);
+
+	av_rxlev = get_meas_rep_avg(mr->lchan, MEAS_REP_DL_RXLEV_FULL,
+				    net->handover.win_rxlev_avg);
+
+	/* Interference HO */
+	if (rxlev2dbm(av_rxlev) > -85 &&
+	    meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL,
+				   3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Bad Quality */
+	if (meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL,
+				   3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Low Level */
+	if (rxlev2dbm(av_rxlev) <= -110)
+		return attempt_handover(mr);
+
+	/* Distance */
+	if (mr->ms_l1.ta > net->handover.max_distance)
+		return attempt_handover(mr);
+
+	/* Power Budget AKA Better Cell */
+	if ((mr->nr % net->handover.pwr_interval) == 0)
+		return attempt_handover(mr);
+
+	return 0;
+
+}
+
+static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct lchan_signal_data *lchan_data;
+
+	if (subsys != SS_LCHAN)
+		return 0;
+
+	lchan_data = signal_data;
+	switch (signal) {
+	case S_LCHAN_MEAS_REP:
+		process_meas_rep(lchan_data->mr);
+		break;
+	}
+
+	return 0;
+}
+
+void on_dso_load_ho_dec(void)
+{
+	register_signal_handler(SS_LCHAN, ho_dec_sig_cb, NULL);
+}
diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c
new file mode 100644
index 0000000..c2e3f8c
--- /dev/null
+++ b/src/libbsc/handover_logic.c
@@ -0,0 +1,393 @@
+/* Handover Logic for Inter-BTS (Intra-BSC) Handover.  This does not
+ * actually implement the handover algorithm/decision, but executes a
+ * handover decision */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+struct bsc_handover {
+	struct llist_head list;
+
+	struct gsm_lchan *old_lchan;
+	struct gsm_lchan *new_lchan;
+
+	struct timer_list T3103;
+
+	u_int8_t ho_ref;
+};
+
+static LLIST_HEAD(bsc_handovers);
+
+static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	llist_for_each_entry(ho, &bsc_handovers, list) {
+		if (ho->new_lchan == new_lchan)
+			return ho;
+	}
+
+	return NULL;
+}
+
+static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
+{
+	struct bsc_handover *ho;
+
+	llist_for_each_entry(ho, &bsc_handovers, list) {
+		if (ho->old_lchan == old_lchan)
+			return ho;
+	}
+
+	return NULL;
+}
+
+/* Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm,
+ * after it has decided it wants to initiate HO to a specific BTS */
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts)
+{
+	struct gsm_lchan *new_lchan;
+	struct bsc_handover *ho;
+	static u_int8_t ho_ref;
+	int rc;
+
+	/* don't attempt multiple handovers for the same lchan at
+	 * the same time */
+	if (bsc_ho_by_old_lchan(old_lchan))
+		return -EBUSY;
+
+	DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n",
+		old_lchan->ts->trx->bts->nr, bts->nr);
+
+	counter_inc(bts->network->stats.handover.attempted);
+
+	if (!old_lchan->conn) {
+		LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n");
+		return -ENOSPC;
+	}
+
+	new_lchan = lchan_alloc(bts, old_lchan->type, 0);
+	if (!new_lchan) {
+		LOGP(DHO, LOGL_NOTICE, "No free channel\n");
+		counter_inc(bts->network->stats.handover.no_channel);
+		return -ENOSPC;
+	}
+
+	ho = talloc_zero(tall_bsc_ctx, struct bsc_handover);
+	if (!ho) {
+		LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
+		lchan_free(new_lchan);
+		return -ENOMEM;
+	}
+	ho->old_lchan = old_lchan;
+	ho->new_lchan = new_lchan;
+	ho->ho_ref = ho_ref++;
+
+	/* copy some parameters from old lchan */
+	memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr));
+	new_lchan->ms_power = old_lchan->ms_power;
+	new_lchan->bs_power = old_lchan->bs_power;
+	new_lchan->rsl_cmode = old_lchan->rsl_cmode;
+	new_lchan->tch_mode = old_lchan->tch_mode;
+
+	new_lchan->conn = old_lchan->conn;
+	new_lchan->conn->ho_lchan = new_lchan;
+
+	/* FIXME: do we have a better idea of the timing advance? */
+	rc = rsl_chan_activate_lchan(new_lchan, RSL_ACT_INTER_ASYNC, 0,
+				     ho->ho_ref);
+	if (rc < 0) {
+		LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+		new_lchan->conn->ho_lchan = NULL;
+		new_lchan->conn = NULL;
+		talloc_free(ho);
+		lchan_free(new_lchan);
+		return rc;
+	}
+
+	rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+	llist_add(&ho->list, &bsc_handovers);
+	/* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
+
+	return 0;
+}
+
+void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(conn->ho_lchan);
+
+
+	if (!ho && conn->ho_lchan)
+		LOGP(DHO, LOGL_ERROR, "BUG: We lost some state.\n");
+
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return;
+	}
+
+	conn->ho_lchan->conn = NULL;
+	conn->ho_lchan = NULL;
+
+	if (free_lchan)
+		lchan_release(ho->new_lchan, 0, 1);
+
+	bsc_del_timer(&ho->T3103);
+	llist_del(&ho->list);
+	talloc_free(ho);
+}
+
+/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */
+static void ho_T3103_cb(void *_ho)
+{
+	struct bsc_handover *ho = _ho;
+	struct gsm_network *net = ho->new_lchan->ts->trx->bts->network;
+
+	DEBUGP(DHO, "HO T3103 expired\n");
+	counter_inc(net->stats.handover.timeout);
+
+	ho->new_lchan->conn->ho_lchan = NULL;
+	ho->new_lchan->conn = NULL;
+	lchan_release(ho->new_lchan, 0, 1);
+	llist_del(&ho->list);
+	talloc_free(ho);
+}
+
+/* RSL has acknowledged activation of the new lchan */
+static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+	int rc;
+
+	/* we need to check if this channel activation is related to
+	 * a handover at all (and if, which particular handover) */
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho)
+		return -ENODEV;
+
+	DEBUGP(DHO, "handover activate ack, send HO Command\n");
+
+	/* we can now send the 04.08 HANDOVER COMMAND to the MS
+	 * using the old lchan */
+
+	rc = gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref);
+
+	/* start T3103.  We can continue either with T3103 expiration,
+	 * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */
+	ho->T3103.cb = ho_T3103_cb;
+	ho->T3103.data = ho;
+	bsc_schedule_timer(&ho->T3103, 10, 0);
+
+	/* create a RTP connection */
+	if (is_ipaccess_bts(new_lchan->ts->trx->bts))
+		rsl_ipacc_crcx(new_lchan);
+
+	return 0;
+}
+
+/* RSL has not acknowledged activation of the new lchan */
+static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	new_lchan->conn->ho_lchan = NULL;
+	new_lchan->conn = NULL;
+	llist_del(&ho->list);
+	talloc_free(ho);
+
+	/* FIXME: maybe we should try to allocate a new LCHAN here? */
+
+	return 0;
+}
+
+/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */
+static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
+{
+	struct gsm_network *net;
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	net = new_lchan->ts->trx->bts->network;
+	LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN "
+	     "%u->%u\n", subscr_name(ho->old_lchan->conn->subscr),
+	     ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr,
+	     ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn);
+
+	counter_inc(net->stats.handover.completed);
+
+	bsc_del_timer(&ho->T3103);
+
+	/* Replace the ho lchan with the primary one */
+	if (ho->old_lchan != new_lchan->conn->lchan)
+		LOGP(DHO, LOGL_ERROR, "Primary lchan changed during handover.\n");
+
+	if (new_lchan != new_lchan->conn->ho_lchan)
+		LOGP(DHO, LOGL_ERROR, "Handover channel changed during this handover.\n");
+
+	new_lchan->conn->ho_lchan = NULL;
+	new_lchan->conn->lchan = new_lchan;
+	ho->old_lchan->conn = NULL;
+
+	rsl_lchan_set_state(ho->old_lchan, LCHAN_S_INACTIVE);
+	lchan_release(ho->old_lchan, 0, 1);
+
+	/* do something to re-route the actual speech frames ! */
+
+	llist_del(&ho->list);
+	talloc_free(ho);
+
+	return 0;
+}
+
+/* GSM 04.08 HANDOVER FAIL has been received */
+static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
+{
+	struct gsm_network *net = old_lchan->ts->trx->bts->network;
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_old_lchan(old_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	counter_inc(net->stats.handover.failed);
+
+	bsc_del_timer(&ho->T3103);
+	llist_del(&ho->list);
+
+	/* release the channel and forget about it */
+	ho->new_lchan->conn->ho_lchan = NULL;
+	ho->new_lchan->conn = NULL;
+	lchan_release(ho->new_lchan, 0, 1);
+
+	talloc_free(ho);
+
+	return 0;
+}
+
+/* GSM 08.58 HANDOVER DETECT has been received */
+static int ho_rsl_detect(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	/* FIXME: do we actually want to do something here ? */
+
+	return 0;
+}
+
+static int ho_ipac_crcx_ack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+	struct ho_signal_data sig_ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		/* it is perfectly normal, we have CRCX even in non-HO cases */
+		return 0;
+	}
+
+	sig_ho.old_lchan = ho->old_lchan;
+	sig_ho.new_lchan = new_lchan;
+	dispatch_signal(SS_HO, S_HANDOVER_ACK, &sig_ho);
+	return 0;
+}
+
+static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
+			   void *handler_data, void *signal_data)
+{
+	struct lchan_signal_data *lchan_data;
+	struct gsm_lchan *lchan;
+
+	lchan_data = signal_data;
+	switch (subsys) {
+	case SS_LCHAN:
+		lchan = lchan_data->lchan;
+		switch (signal) {
+		case S_LCHAN_ACTIVATE_ACK:
+			return ho_chan_activ_ack(lchan);
+		case S_LCHAN_ACTIVATE_NACK:
+			return ho_chan_activ_nack(lchan);
+		case S_LCHAN_HANDOVER_DETECT:
+			return ho_rsl_detect(lchan);
+		case S_LCHAN_HANDOVER_COMPL:
+			return ho_gsm48_ho_compl(lchan);
+		case S_LCHAN_HANDOVER_FAIL:
+			return ho_gsm48_ho_fail(lchan);
+		}
+		break;
+	case SS_ABISIP:
+		lchan = signal_data;
+		switch (signal) {
+		case S_ABISIP_CRCX_ACK:
+			return ho_ipac_crcx_ack(lchan);
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_ho_logic(void)
+{
+	register_signal_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
+	register_signal_handler(SS_ABISIP, ho_logic_sig_cb, NULL);
+}
diff --git a/src/libbsc/meas_rep.c b/src/libbsc/meas_rep.c
new file mode 100644
index 0000000..788a9ba
--- /dev/null
+++ b/src/libbsc/meas_rep.c
@@ -0,0 +1,116 @@
+/* Measurement Report Processing */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+
+static int get_field(const struct gsm_meas_rep *rep,
+		     enum meas_rep_field field)
+{
+	switch (field) {
+	case MEAS_REP_DL_RXLEV_FULL:
+		return rep->dl.full.rx_lev;
+	case MEAS_REP_DL_RXLEV_SUB:
+		return rep->dl.sub.rx_lev;
+	case MEAS_REP_DL_RXQUAL_FULL:
+		return rep->dl.full.rx_qual;
+	case MEAS_REP_DL_RXQUAL_SUB:
+		return rep->dl.sub.rx_qual;
+	case MEAS_REP_UL_RXLEV_FULL:
+		return rep->ul.full.rx_lev;
+	case MEAS_REP_UL_RXLEV_SUB:
+		return rep->ul.sub.rx_lev;
+	case MEAS_REP_UL_RXQUAL_FULL:
+		return rep->ul.full.rx_qual;
+	case MEAS_REP_UL_RXQUAL_SUB:
+		return rep->ul.sub.rx_qual;
+	}
+
+	return 0;
+}
+
+
+unsigned int calc_initial_idx(unsigned int array_size,
+			      unsigned int meas_rep_idx,
+			      unsigned int num_values)
+{
+	int offs, idx;
+
+	/* from which element do we need to start if we're interested
+	 * in an average of 'num' elements */
+	offs = meas_rep_idx - num_values;
+
+	if (offs < 0)
+		idx = array_size + offs;
+	else
+		idx = offs;
+
+	return idx;
+}
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+		     enum meas_rep_field field, unsigned int num)
+{
+	unsigned int i, idx;
+	int avg = 0;
+
+	if (num < 1)
+		return 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				lchan->meas_rep_idx, num);
+
+	for (i = 0; i < num; i++) {
+		int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep);
+
+		avg += get_field(&lchan->meas_rep[j], field);
+	}
+
+	return avg / num;
+}
+
+/* Check if N out of M last values for FIELD are >= bd */
+int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
+			enum meas_rep_field field,
+			unsigned int n, unsigned int m, int be)
+{
+	unsigned int i, idx;
+	int count = 0;
+
+	idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+				lchan->meas_rep_idx, m);
+
+	for (i = 0; i < m; i++) {
+		int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep);
+		int val = get_field(&lchan->meas_rep[j], field);
+
+		if (val >= be)
+			count++;
+
+		if (count >= n)
+			return 1;
+	}
+
+	return 0;
+}
diff --git a/src/libbsc/paging.c b/src/libbsc/paging.c
new file mode 100644
index 0000000..6502545
--- /dev/null
+++ b/src/libbsc/paging.c
@@ -0,0 +1,395 @@
+/* Paging helper and manager.... */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * Relevant specs:
+ *     12.21:
+ *       - 9.4.12 for CCCH Local Threshold
+ *
+ *     05.58:
+ *       - 8.5.2 CCCH Load indication
+ *       - 9.3.15 Paging Load
+ *
+ * Approach:
+ *       - Send paging command to subscriber
+ *       - On Channel Request we will remember the reason
+ *       - After the ACK we will request the identity
+ *	 - Then we will send assign the gsm_subscriber and
+ *	 - and call a callback
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <openbsc/paging.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+void *tall_paging_ctx;
+
+#define PAGING_TIMER 0, 500000
+
+static unsigned int calculate_group(struct gsm_bts *bts, struct gsm_subscriber *subscr)
+{
+	int ccch_conf;
+	int bs_cc_chans;
+	int blocks;
+	unsigned int group;
+	
+	ccch_conf = bts->si_common.chan_desc.ccch_conf;
+	bs_cc_chans = rsl_ccch_conf_to_bs_cc_chans(ccch_conf);
+	/* code word + 2, as 2 channels equals 0x0 */
+	blocks = rsl_number_of_paging_subchannels(bts);
+	group = get_paging_group(str_to_imsi(subscr->imsi),
+					bs_cc_chans, blocks);
+	return group;
+}
+
+/*
+ * Kill one paging request update the internal list...
+ */
+static void paging_remove_request(struct gsm_bts_paging_state *paging_bts,
+				struct gsm_paging_request *to_be_deleted)
+{
+	bsc_del_timer(&to_be_deleted->T3113);
+	llist_del(&to_be_deleted->entry);
+	subscr_put(to_be_deleted->subscr);
+	talloc_free(to_be_deleted);
+}
+
+static void page_ms(struct gsm_paging_request *request)
+{
+	u_int8_t mi[128];
+	unsigned int mi_len;
+	unsigned int page_group;
+
+	LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: '%s' tmsi: '0x%x'\n",
+		request->subscr->imsi, request->subscr->tmsi);
+
+	if (request->subscr->tmsi == GSM_RESERVED_TMSI)
+		mi_len = gsm48_generate_mid_from_imsi(mi, request->subscr->imsi);
+	else
+		mi_len = gsm48_generate_mid_from_tmsi(mi, request->subscr->tmsi);
+
+	page_group = calculate_group(request->bts, request->subscr);
+	gsm0808_page(request->bts, page_group, mi_len, mi, request->chan_type);
+}
+
+static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts)
+{
+	if (llist_empty(&paging_bts->pending_requests))
+		return;
+
+	if (!bsc_timer_pending(&paging_bts->work_timer))
+		bsc_schedule_timer(&paging_bts->work_timer, PAGING_TIMER);
+}
+
+
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts);
+static void paging_give_credit(void *data)
+{
+	struct gsm_bts_paging_state *paging_bts = data;
+
+	LOGP(DPAG, LOGL_NOTICE, "No slots available on bts nr %d\n", paging_bts->bts->nr);
+	paging_bts->available_slots = 20;
+	paging_handle_pending_requests(paging_bts);
+}
+
+static int can_send_pag_req(struct gsm_bts *bts, int rsl_type)
+{
+	struct pchan_load pl;
+	int count;
+
+	memset(&pl, 0, sizeof(pl));
+	bts_chan_load(&pl, bts);
+
+	switch (rsl_type) {
+	case RSL_CHANNEED_TCH_F:
+	case RSL_CHANNEED_TCH_ForH:
+		goto count_tch;
+		break;
+	case RSL_CHANNEED_SDCCH:
+		goto count_sdcch;
+		break;
+	case RSL_CHANNEED_ANY:
+	default:
+		if (bts->network->pag_any_tch)
+			goto count_tch;
+		else
+			goto count_sdcch;
+		break;
+	}
+
+	return 0;
+
+	/* could available SDCCH */
+count_sdcch:
+	count = 0;
+	count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total
+			- pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used;
+	count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total
+			- pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used;
+	return bts->paging.free_chans_need > count;
+
+count_tch:
+	count = 0;
+	count += pl.pchan[GSM_PCHAN_TCH_F].total
+			- pl.pchan[GSM_PCHAN_TCH_F].used;
+	if (bts->network->neci)
+		count += pl.pchan[GSM_PCHAN_TCH_H].total
+				- pl.pchan[GSM_PCHAN_TCH_H].used;
+	return bts->paging.free_chans_need > count;
+}
+
+/*
+ * This is kicked by the periodic PAGING LOAD Indicator
+ * coming from abis_rsl.c
+ *
+ * We attempt to iterate once over the list of items but
+ * only upto available_slots.
+ */
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts)
+{
+	struct gsm_paging_request *request = NULL;
+
+	/*
+	 * Determine if the pending_requests list is empty and
+	 * return then.
+	 */
+	if (llist_empty(&paging_bts->pending_requests)) {
+		/* since the list is empty, no need to reschedule the timer */
+		return;
+	}
+
+	/*
+	 * In case the BTS does not provide us with load indication and we
+	 * ran out of slots, call an autofill routine. It might be that the
+	 * BTS did not like our paging messages and then we have counted down
+	 * to zero and we do not get any messages.
+	 */
+	if (paging_bts->available_slots == 0) {
+		paging_bts->credit_timer.cb = paging_give_credit;
+		paging_bts->credit_timer.data = paging_bts;
+		bsc_schedule_timer(&paging_bts->credit_timer, 5, 0);
+		return;
+	}
+
+	request = llist_entry(paging_bts->pending_requests.next,
+			      struct gsm_paging_request, entry);
+
+	/* we need to determine the number of free channels */
+	if (paging_bts->free_chans_need != -1) {
+		if (can_send_pag_req(request->bts, request->chan_type) != 0)
+			goto skip_paging;
+	}
+
+	/* handle the paging request now */
+	page_ms(request);
+	paging_bts->available_slots--;
+	request->attempts++;
+
+	/* take the current and add it to the back */
+	llist_del(&request->entry);
+	llist_add_tail(&request->entry, &paging_bts->pending_requests);
+
+skip_paging:
+	bsc_schedule_timer(&paging_bts->work_timer, PAGING_TIMER);
+}
+
+static void paging_worker(void *data)
+{
+	struct gsm_bts_paging_state *paging_bts = data;
+
+	paging_handle_pending_requests(paging_bts);
+}
+
+void paging_init(struct gsm_bts *bts)
+{
+	bts->paging.bts = bts;
+	INIT_LLIST_HEAD(&bts->paging.pending_requests);
+	bts->paging.work_timer.cb = paging_worker;
+	bts->paging.work_timer.data = &bts->paging;
+
+	/* Large number, until we get a proper message */
+	bts->paging.available_slots = 20;
+}
+
+static int paging_pending_request(struct gsm_bts_paging_state *bts,
+				struct gsm_subscriber *subscr) {
+	struct gsm_paging_request *req;
+
+	llist_for_each_entry(req, &bts->pending_requests, entry) {
+		if (subscr == req->subscr)
+			return 1;
+	}
+
+	return 0;	
+}
+
+static void paging_T3113_expired(void *data)
+{
+	struct gsm_paging_request *req = (struct gsm_paging_request *)data;
+	void *cbfn_param;
+	gsm_cbfn *cbfn;
+	int msg;
+
+	LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n",
+		req, req->subscr->imsi);
+
+	/* must be destroyed before calling cbfn, to prevent double free */
+	counter_inc(req->bts->network->stats.paging.expired);
+	cbfn_param = req->cbfn_param;
+	cbfn = req->cbfn;
+
+	/* did we ever manage to page the subscriber */
+	msg = req->attempts > 0 ? GSM_PAGING_EXPIRED : GSM_PAGING_BUSY;
+
+	/* destroy it now. Do not access req afterwards */
+	paging_remove_request(&req->bts->paging, req);
+
+	if (cbfn)
+		cbfn(GSM_HOOK_RR_PAGING, msg, NULL, NULL,
+			  cbfn_param);
+}
+
+static int _paging_request(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+			    int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req;
+
+	if (paging_pending_request(bts_entry, subscr)) {
+		LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n", subscr->imsi);
+		return -EEXIST;
+	}
+
+	LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %llu on bts %d.\n",
+		subscr->id, bts->nr);
+	req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
+	req->subscr = subscr_get(subscr);
+	req->bts = bts;
+	req->chan_type = type;
+	req->cbfn = cbfn;
+	req->cbfn_param = data;
+	req->T3113.cb = paging_T3113_expired;
+	req->T3113.data = req;
+	bsc_schedule_timer(&req->T3113, bts->network->T3113, 0);
+	llist_add_tail(&req->entry, &bts_entry->pending_requests);
+	paging_schedule_if_needed(bts_entry);
+
+	return 0;
+}
+
+int paging_request(struct gsm_network *network, struct gsm_subscriber *subscr,
+		   int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts *bts = NULL;
+	int num_pages = 0;
+
+	counter_inc(network->stats.paging.attempted);
+
+	/* start paging subscriber on all BTS within Location Area */
+	do {
+		int rc;
+
+		bts = gsm_bts_by_lac(network, subscr->lac, bts);
+		if (!bts)
+			break;
+
+		/* skip all currently inactive TRX */
+		if (!trx_is_usable(bts->c0))
+			continue;
+
+		num_pages++;
+
+		/* Trigger paging, pass any error to caller */
+		rc = _paging_request(bts, subscr, type, cbfn, data);
+		if (rc < 0)
+			return rc;
+	} while (1);
+
+	if (num_pages == 0)
+		counter_inc(network->stats.paging.detached);
+
+	return num_pages;
+}
+
+
+/* we consciously ignore the type of the request here */
+static void _paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+				 struct gsm_subscriber_connection *conn,
+				 struct msgb *msg)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req, *req2;
+
+	llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
+				 entry) {
+		if (req->subscr == subscr) {
+			if (conn && req->cbfn) {
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d, calling cbfn.\n", bts->nr);
+				req->cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+					  msg, conn, req->cbfn_param);
+			} else
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d silently.\n", bts->nr);
+			paging_remove_request(&bts->paging, req);
+			break;
+		}
+	}
+}
+
+/* Stop paging on all other bts' */
+void paging_request_stop(struct gsm_bts *_bts, struct gsm_subscriber *subscr,
+			 struct gsm_subscriber_connection *conn,
+			 struct msgb *msg)
+{
+	struct gsm_bts *bts = NULL;
+
+	if (_bts)
+		_paging_request_stop(_bts, subscr, conn, msg);
+
+	do {
+		/*
+		 * FIXME: Don't use the lac of the subscriber...
+		 * as it might have magically changed the lac.. use the
+		 * location area of the _bts as reconfiguration of the
+		 * network is probably happening less often.
+		 */
+		bts = gsm_bts_by_lac(subscr->net, subscr->lac, bts);
+		if (!bts)
+			break;
+
+		/* Stop paging */
+		if (bts != _bts)
+			_paging_request_stop(bts, subscr, NULL, NULL);
+	} while (1);
+}
+
+void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t free_slots)
+{
+	bsc_del_timer(&bts->paging.credit_timer);
+	bts->paging.available_slots = free_slots;
+	paging_schedule_if_needed(&bts->paging);
+}
diff --git a/src/libbsc/rest_octets.c b/src/libbsc/rest_octets.c
new file mode 100644
index 0000000..084f144
--- /dev/null
+++ b/src/libbsc/rest_octets.c
@@ -0,0 +1,432 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface,
+ * rest octet handling according to
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/bitvec.h>
+#include <openbsc/rest_octets.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 1;
+
+	if (nch_pos) {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, *nch_pos, 5);
+	} else
+		bitvec_set_bit(&bv, L);
+
+	bitvec_spare_padding(&bv, 7);
+	return bv.data_len;
+}
+
+/* Append selection parameters to bitvec */
+static void append_selection_params(struct bitvec *bv,
+				    const struct gsm48_si_selection_params *sp)
+{
+	if (sp->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_bit(bv, sp->cbq);
+		bitvec_set_uint(bv, sp->cell_resel_off, 6);
+		bitvec_set_uint(bv, sp->temp_offs, 3);
+		bitvec_set_uint(bv, sp->penalty_time, 5);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+/* Append power offset to bitvec */
+static void append_power_offset(struct bitvec *bv,
+				const struct gsm48_si_power_offset *po)
+{
+	if (po->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_uint(bv, po->power_offset, 2);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+/* Append GPRS indicator to bitvec */
+static void append_gprs_ind(struct bitvec *bv,
+			    const struct gsm48_si3_gprs_ind *gi)
+{
+	if (gi->present) {
+		bitvec_set_bit(bv, H);
+		bitvec_set_uint(bv, gi->ra_colour, 3);
+		/* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */
+		bitvec_set_bit(bv, gi->si13_position);
+	} else
+		bitvec_set_bit(bv, L);
+}
+
+
+/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
+int rest_octets_si3(u_int8_t *data, const struct gsm48_si_ro_info *si3)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 4;
+
+	/* Optional Selection Parameters */
+	append_selection_params(&bv, &si3->selection_params);
+
+	/* Optional Power Offset */
+	append_power_offset(&bv, &si3->power_offset);
+
+	/* Do we have a SI2ter on the BCCH? */
+	if (si3->si2ter_indicator)
+		bitvec_set_bit(&bv, H);
+	else
+		bitvec_set_bit(&bv, L);
+
+	/* Early Classmark Sending Control */
+	if (si3->early_cm_ctrl)
+		bitvec_set_bit(&bv, H);
+	else
+		bitvec_set_bit(&bv, L);
+
+	/* Do we have a SI Type 9 on the BCCH? */
+	if (si3->scheduling.present) {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, si3->scheduling.where, 3);
+	} else
+		bitvec_set_bit(&bv, L);
+
+	/* GPRS Indicator */
+	append_gprs_ind(&bv, &si3->gprs_ind);
+
+	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+	return bv.data_len;
+}
+
+static int append_lsa_params(struct bitvec *bv,
+			     const struct gsm48_lsa_params *lsa_params)
+{
+	/* FIXME */
+	return -1;
+}
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(u_int8_t *data, const struct gsm48_si_ro_info *si4)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 10; /* FIXME: up to ? */
+
+	/* SI4 Rest Octets O */
+	append_selection_params(&bv, &si4->selection_params);
+	append_power_offset(&bv, &si4->power_offset);
+	append_gprs_ind(&bv, &si4->gprs_ind);
+
+	if (0 /* FIXME */) {
+		/* H and SI4 Rest Octets S */
+		bitvec_set_bit(&bv, H);
+
+		/* LSA Parameters */
+		if (si4->lsa_params.present) {
+			bitvec_set_bit(&bv, H);
+			append_lsa_params(&bv, &si4->lsa_params);
+		} else
+			bitvec_set_bit(&bv, L);
+
+		/* Cell Identity */
+		if (1) {
+			bitvec_set_bit(&bv, H);
+			bitvec_set_uint(&bv, si4->cell_id, 16);
+		} else
+			bitvec_set_bit(&bv, L);
+
+		/* LSA ID Information */
+		if (0) {
+			bitvec_set_bit(&bv, H);
+			/* FIXME */
+		} else
+			bitvec_set_bit(&bv, L);
+	} else {
+		/* L and break indicator */
+		bitvec_set_bit(&bv, L);
+		bitvec_set_bit(&bv, si4->break_ind ? H : L);
+	}
+
+	return bv.data_len;
+}
+
+/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
+   < GPRS Mobile Allocation IE > ::=
+     < HSN : bit (6) >
+     { 0 | 1 < RFL number list : < RFL number list struct > > }
+     { 0 < MA_LENGTH : bit (6) >
+         < MA_BITMAP: bit (val(MA_LENGTH) + 1) >
+     | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ;
+
+     < RFL number list struct > :: =
+       < RFL_NUMBER : bit (4) >
+       { 0 | 1 < RFL number list struct > } ;
+     < ARFCN index list struct > ::=
+       < ARFCN_INDEX : bit(6) >
+       { 0 | 1 < ARFCN index list struct > } ;
+ */
+static int append_gprs_mobile_alloc(struct bitvec *bv)
+{
+	/* Hopping Sequence Number */
+	bitvec_set_uint(bv, 0, 6);
+
+	if (0) {
+		/* We want to use a RFL number list */
+		bitvec_set_bit(bv, 1);
+		/* FIXME: RFL number list */
+	} else
+		bitvec_set_bit(bv, 0);
+
+	if (0) {
+		/* We want to use a MA_BITMAP */
+		bitvec_set_bit(bv, 0);
+		/* FIXME: MA_LENGTH, MA_BITMAP, ... */
+	} else {
+		bitvec_set_bit(bv, 1);
+		if (0) {
+			/* We want to provide an ARFCN index list */
+			bitvec_set_bit(bv, 1);
+			/* FIXME */
+		} else
+			bitvec_set_bit(bv, 0);
+	}
+	return 0;
+}
+
+static int encode_t3192(unsigned int t3192)
+{
+	if (t3192 == 0)
+		return 3;
+	else if (t3192 <= 80)
+		return 4;
+	else if (t3192 <= 120)
+		return 5;
+	else if (t3192 <= 160)
+		return 6;
+	else if (t3192 <= 200)
+		return 7;
+	else if (t3192 <= 500)
+		return 0;
+	else if (t3192 <= 1000)
+		return 1;
+	else if (t3192 <= 1500)
+		return 2;
+	else
+		return -EINVAL;
+}
+
+static int encode_drx_timer(unsigned int drx)
+{
+	if (drx == 0)
+		return 0;
+	else if (drx == 1)
+		return 1;
+	else if (drx == 2)
+		return 2;
+	else if (drx <= 4)
+		return 3;
+	else if (drx <= 8)
+		return 4;
+	else if (drx <= 16)
+		return 5;
+	else if (drx <= 32)
+		return 6;
+	else if (drx <= 64)
+		return 7;
+	else
+		return -EINVAL;
+}
+
+/* GPRS Cell Options as per TS 04.60 Chapter 12.24
+	< GPRS Cell Options IE > ::=
+		< NMO : bit(2) >
+		< T3168 : bit(3) >
+		< T3192 : bit(3) >
+		< DRX_TIMER_MAX: bit(3) >
+		< ACCESS_BURST_TYPE: bit >
+		< CONTROL_ACK_TYPE : bit >
+		< BS_CV_MAX: bit(4) >
+		{ 0 | 1 < PAN_DEC : bit(3) >
+			< PAN_INC : bit(3) >
+			< PAN_MAX : bit(3) >
+		{ 0 | 1 < Extension Length : bit(6) >
+			< bit (val(Extension Length) + 1
+			& { < Extension Information > ! { bit ** = <no string> } } ;
+	< Extension Information > ::=
+		{ 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit >
+			< BEP_PERIOD : bit(4) > }
+		< PFC_FEATURE_MODE : bit >
+		< DTM_SUPPORT : bit >
+		<BSS_PAGING_COORDINATION: bit >
+		<spare bit > ** ;
+ */
+static int append_gprs_cell_opt(struct bitvec *bv,
+				const struct gprs_cell_options *gco)
+{
+	int t3192, drx_timer_max;
+
+	t3192 = encode_t3192(gco->t3192);
+	if (t3192 < 0)
+		return t3192;
+
+	drx_timer_max = encode_drx_timer(gco->drx_timer_max);
+	if (drx_timer_max < 0)
+		return drx_timer_max;
+
+	bitvec_set_uint(bv, gco->nmo, 2);
+	bitvec_set_uint(bv, gco->t3168 / 500, 3);
+	bitvec_set_uint(bv, t3192, 3);
+	bitvec_set_uint(bv, drx_timer_max, 3);
+	/* ACCESS_BURST_TYPE: Hard-code 8bit */
+	bitvec_set_bit(bv, 0);
+	/* CONTROL_ACK_TYPE: Hard-code to RLC/MAC control block */
+	bitvec_set_bit(bv, 1);
+	bitvec_set_uint(bv, gco->bs_cv_max, 4);
+
+	if (0) {
+		/* hard-code no PAN_{DEC,INC,MAX} */
+		bitvec_set_bit(bv, 0);
+	} else {
+		/* copied from ip.access BSC protocol trace */
+		bitvec_set_bit(bv, 1);
+		bitvec_set_uint(bv, 1, 3);	/* DEC */
+		bitvec_set_uint(bv, 1, 3);	/* INC */
+		bitvec_set_uint(bv, 15, 3);	/* MAX */
+	}
+
+	if (!gco->ext_info_present) {
+		/* no extension information */
+		bitvec_set_bit(bv, 0);
+	} else {
+		/* extension information */
+		bitvec_set_bit(bv, 1);
+		if (!gco->ext_info.egprs_supported) {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 0);
+		} else {
+			/* 6bit length of extension */
+			bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+			/* EGPRS supported in the cell */
+			bitvec_set_bit(bv, 1);
+			/* 1bit EGPRS PACKET CHANNEL REQUEST */
+			bitvec_set_bit(bv, gco->ext_info.use_egprs_p_ch_req);
+			/* 4bit BEP PERIOD */
+			bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
+		}
+		bitvec_set_bit(bv, gco->ext_info.pfc_supported);
+		bitvec_set_bit(bv, gco->ext_info.dtm_supported);
+		bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+	}
+
+	return 0;
+}
+
+static void append_gprs_pwr_ctrl_pars(struct bitvec *bv,
+				      const struct gprs_power_ctrl_pars *pcp)
+{
+	bitvec_set_uint(bv, pcp->alpha, 4);
+	bitvec_set_uint(bv, pcp->t_avg_w, 5);
+	bitvec_set_uint(bv, pcp->t_avg_t, 5);
+	bitvec_set_uint(bv, pcp->pc_meas_chan, 1);
+	bitvec_set_uint(bv, pcp->n_avg_i, 4);
+}
+
+/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
+int rest_octets_si13(u_int8_t *data, const struct gsm48_si13_info *si13)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 20;
+
+	if (0) {
+		/* No rest octets */
+		bitvec_set_bit(&bv, L);
+	} else {
+		bitvec_set_bit(&bv, H);
+		bitvec_set_uint(&bv, si13->bcch_change_mark, 3);
+		bitvec_set_uint(&bv, si13->si_change_field, 4);
+		if (1) {
+			bitvec_set_bit(&bv, 0);
+		} else {
+			bitvec_set_bit(&bv, 1);
+			bitvec_set_uint(&bv, si13->bcch_change_mark, 2);
+			append_gprs_mobile_alloc(&bv);
+		}
+		if (!si13->pbcch_present) {
+			/* PBCCH not present in cell */
+			bitvec_set_bit(&bv, 0);
+			bitvec_set_uint(&bv, si13->no_pbcch.rac, 8);
+			bitvec_set_bit(&bv, si13->no_pbcch.spgc_ccch_sup);
+			bitvec_set_uint(&bv, si13->no_pbcch.prio_acc_thr, 3);
+			bitvec_set_uint(&bv, si13->no_pbcch.net_ctrl_ord, 2);
+			append_gprs_cell_opt(&bv, &si13->cell_opts);
+			append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars);
+		} else {
+			/* PBCCH present in cell */
+			bitvec_set_bit(&bv, 1);
+			bitvec_set_uint(&bv, si13->pbcch.psi1_rep_per, 4);
+			/* PBCCH Descripiton */
+			bitvec_set_uint(&bv, si13->pbcch.pb, 4);
+			bitvec_set_uint(&bv, si13->pbcch.tsc, 3);
+			bitvec_set_uint(&bv, si13->pbcch.tn, 3);
+			switch (si13->pbcch.carrier_type) {
+			case PBCCH_BCCH:
+				bitvec_set_bit(&bv, 0);
+				bitvec_set_bit(&bv, 0);
+				break;
+			case PBCCH_ARFCN:
+				bitvec_set_bit(&bv, 0);
+				bitvec_set_bit(&bv, 1);
+				bitvec_set_uint(&bv, si13->pbcch.arfcn, 10);
+				break;
+			case PBCCH_MAIO:
+				bitvec_set_bit(&bv, 1);
+				bitvec_set_uint(&bv, si13->pbcch.maio, 6);
+				break;
+			}
+		}
+		/* 3GPP TS 44.018 Release 6 / 10.5.2.37b */
+		bitvec_set_bit(&bv, H);	/* added Release 99 */
+		/* claim our SGSN is compatible with Release 99, as EDGE and EGPRS
+		 * was only added in this Release */
+		bitvec_set_bit(&bv, 1);
+	}
+	bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+	return bv.data_len;
+}
diff --git a/src/libbsc/system_information.c b/src/libbsc/system_information.c
new file mode 100644
index 0000000..dc71938
--- /dev/null
+++ b/src/libbsc/system_information.c
@@ -0,0 +1,616 @@
+/* GSM 04.08 System Information (SI) encoding and decoding
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/rest_octets.h>
+#include <osmocore/bitvec.h>
+#include <osmocore/utils.h>
+#include <openbsc/debug.h>
+
+#define GSM48_CELL_CHAN_DESC_SIZE	16
+#define GSM_MACBLOCK_PADDING		0x2b
+
+/* verify the sizes of the system information type structs */
+
+/* rest octets are not part of the struct */
+static_assert(sizeof(struct gsm48_system_information_type_header) == 3, _si_header_size);
+static_assert(sizeof(struct gsm48_rach_control) == 3, _si_rach_control);
+static_assert(sizeof(struct gsm48_system_information_type_1) == 22, _si1_size);
+static_assert(sizeof(struct gsm48_system_information_type_2) == 23, _si2_size);
+static_assert(sizeof(struct gsm48_system_information_type_3) == 19, _si3_size);
+static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_size);
+
+/* bs11 forgot the l2 len, 0-6 rest octets */
+static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size);
+static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size);
+
+static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size);
+
+/* Frequency Lists as per TS 04.08 10.5.2.13 */
+
+/* 10.5.2.13.2: Bit map 0 format */
+static int freq_list_bm0_set_arfcn(u_int8_t *chan_list, unsigned int arfcn)
+{
+	unsigned int byte, bit;
+
+	if (arfcn > 124 || arfcn < 1) {
+		LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n");
+		return -EINVAL;
+	}
+
+	/* the bitmask is from 1..124, not from 0..123 */
+	arfcn--;
+
+	byte = arfcn / 8;
+	bit = arfcn % 8;
+
+	chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit);
+
+	return 0;
+}
+
+/* 10.5.2.13.7: Variable bit map format */
+static int freq_list_bmrel_set_arfcn(u_int8_t *chan_list, unsigned int arfcn)
+{
+	unsigned int byte, bit;
+	unsigned int min_arfcn;
+	unsigned int bitno;
+
+	min_arfcn = (chan_list[0] & 1) << 9;
+	min_arfcn |= chan_list[1] << 1;
+	min_arfcn |= (chan_list[2] >> 7) & 1;
+
+	/* The lower end of our bitmaks is always implicitly included */
+	if (arfcn == min_arfcn)
+		return 0;
+
+	if (arfcn < min_arfcn) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) < min(%u)\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+	if (arfcn > min_arfcn + 111) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+
+	bitno = (arfcn - min_arfcn);
+	byte = bitno / 8;
+	bit = bitno % 8;
+
+	chan_list[2 + byte] |= 1 << (7 - bit);
+
+	return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int bitvec2freq_list(u_int8_t *chan_list, struct bitvec *bv,
+			    const struct gsm_bts *bts)
+{
+	int i, rc, min = 1024, max = -1;
+
+	memset(chan_list, 0, 16);
+
+	/* GSM900-only handsets only support 'bit map 0 format' */
+	if (bts->band == GSM_BAND_900) {
+		chan_list[0] = 0;
+
+		for (i = 0; i < bv->data_len*8; i++) {
+			if (bitvec_get_bit_pos(bv, i)) {
+				rc = freq_list_bm0_set_arfcn(chan_list, i);
+				if (rc < 0)
+					return rc;
+			}
+		}
+		return 0;
+	}
+
+	/* We currently only support the 'Variable bitmap format' */
+	chan_list[0] = 0x8e;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i)) {
+			if (i < min)
+				min = i;
+			if (i > max)
+				max = i;
+		}
+	}
+
+	if (max == -1) {
+		/* Empty set, use 'bit map 0 format' */
+		chan_list[0] = 0;
+		return 0;
+	}
+
+	if ((max - min) > 111) {
+		LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, "
+			"distance > 111\n", min, max);
+		return -EINVAL;
+	}
+
+	chan_list[0] |= (min >> 9) & 1;
+	chan_list[1] = (min >> 1);
+	chan_list[2] = (min & 1) << 7;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i)) {
+			rc = freq_list_bmrel_set_arfcn(chan_list, i);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_cell_chan_list(u_int8_t *chan_list, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	struct bitvec *bv = &bts->si_common.cell_alloc;
+
+	/* Zero-initialize the bit-vector */
+	memset(bv->data, 0, bv->data_len);
+
+	/* first we generate a bitvec of all TRX ARFCN's in our BTS */
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		unsigned int i, j;
+		/* Always add the TRX's ARFCN */
+		bitvec_set_bit_pos(bv, trx->arfcn, 1);
+		for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[i];
+			/* Add any ARFCNs present in hopping channels */
+			for (j = 0; j < 1024; j++) {
+				if (bitvec_get_bit_pos(&ts->hopping.arfcns, j))
+					bitvec_set_bit_pos(bv, j, 1);
+			}
+		}
+	}
+
+	/* then we generate a GSM 04.08 frequency list from the bitvec */
+	return bitvec2freq_list(chan_list, bv, bts);
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_bcch_chan_list(u_int8_t *chan_list, struct gsm_bts *bts, int si5)
+{
+	struct gsm_bts *cur_bts;
+	struct bitvec *bv;
+
+	if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP)
+		bv = &bts->si_common.si5_neigh_list;
+	else
+		bv = &bts->si_common.neigh_list;
+
+	/* Generate list of neighbor cells if we are in automatic mode */
+	if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+		/* Zero-initialize the bit-vector */
+		memset(bv->data, 0, bv->data_len);
+
+		/* first we generate a bitvec of the BCCH ARFCN's in our BSC */
+		llist_for_each_entry(cur_bts, &bts->network->bts_list, list) {
+			if (cur_bts == bts)
+				continue;
+			bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1);
+		}
+	}
+
+	/* then we generate a GSM 04.08 frequency list from the bitvec */
+	return bitvec2freq_list(chan_list, bv, bts);
+}
+
+static int generate_si1(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_1 *si1 =
+		(struct gsm48_system_information_type_1 *) output;
+
+	memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si1->header.l2_plen = (21 << 2) | 1;
+	si1->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si1->header.skip_indicator = 0;
+	si1->header.system_information = GSM48_MT_RR_SYSINFO_1;
+
+	rc = generate_cell_chan_list(si1->cell_channel_description, bts);
+	if (rc < 0)
+		return rc;
+
+	si1->rach_control = bts->si_common.rach_control;
+
+	/* SI1 Rest Octets (10.5.2.32), contains NCH position */
+	rc = rest_octets_si1(si1->rest_octets, NULL);
+
+	return sizeof(*si1) + rc;
+}
+
+static int generate_si2(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2 *si2 =
+		(struct gsm48_system_information_type_2 *) output;
+
+	memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2->header.l2_plen = (22 << 2) | 1;
+	si2->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si2->header.skip_indicator = 0;
+	si2->header.system_information = GSM48_MT_RR_SYSINFO_2;
+
+	rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, 0);
+	if (rc < 0)
+		return rc;
+
+	si2->ncc_permitted = bts->si_common.ncc_permitted;
+	si2->rach_control = bts->si_common.rach_control;
+
+	return sizeof(*si2);
+}
+
+static struct gsm48_si_ro_info si_info = {
+	.selection_params = {
+		.present = 0,
+	},
+	.power_offset = {
+		.present = 0,
+	},
+	.si2ter_indicator = 0,
+	.early_cm_ctrl = 1,
+	.scheduling = {
+		.present = 0,
+	},
+	.gprs_ind = {
+		.si13_position = 0,
+		.ra_colour = 0,
+		.present = 1,
+	},
+	.lsa_params = {
+		.present = 0,
+	},
+	.cell_id = 0,	/* FIXME: doesn't the bts have this? */
+	.break_ind = 0,
+};
+
+static int generate_si3(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_3 *si3 =
+		(struct gsm48_system_information_type_3 *) output;
+
+	memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si3->header.l2_plen = (18 << 2) | 1;
+	si3->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si3->header.skip_indicator = 0;
+	si3->header.system_information = GSM48_MT_RR_SYSINFO_3;
+
+	si3->cell_identity = htons(bts->cell_identity);
+	gsm48_generate_lai(&si3->lai, bts->network->country_code,
+			   bts->network->network_code,
+			   bts->location_area_code);
+	si3->control_channel_desc = bts->si_common.chan_desc;
+	si3->cell_options = bts->si_common.cell_options;
+	si3->cell_sel_par = bts->si_common.cell_sel_par;
+	si3->rach_control = bts->si_common.rach_control;
+
+	/* SI3 Rest Octets (10.5.2.34), containing
+		CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME
+		Power Offset, 2ter Indicator, Early Classmark Sending,
+		Scheduling if and WHERE, GPRS Indicator, SI13 position */
+	rc = rest_octets_si3(si3->rest_octets, &si_info);
+
+	return sizeof(*si3) + rc;
+}
+
+static int generate_si4(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_4 *si4 =
+		(struct gsm48_system_information_type_4 *) output;
+
+	/* length of all IEs present except SI4 rest octets and l2_plen */
+	int l2_plen = sizeof(*si4) - 1;
+
+	memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si4->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si4->header.skip_indicator = 0;
+	si4->header.system_information = GSM48_MT_RR_SYSINFO_4;
+
+	gsm48_generate_lai(&si4->lai, bts->network->country_code,
+			   bts->network->network_code,
+			   bts->location_area_code);
+	si4->cell_sel_par = bts->si_common.cell_sel_par;
+	si4->rach_control = bts->si_common.rach_control;
+
+	/* Optional: CBCH Channel Description + CBCH Mobile Allocation */
+
+	si4->header.l2_plen = (l2_plen << 2) | 1;
+
+	/* SI4 Rest Octets (10.5.2.35), containing
+		Optional Power offset, GPRS Indicator,
+		Cell Identity, LSA ID, Selection Parameter */
+	rc = rest_octets_si4(si4->data, &si_info);
+
+	return sizeof(*si4) + rc;
+}
+
+static int generate_si5(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_5 *si5;
+	int rc, l2_plen = 18;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_HSL_FEMTO:
+		*output++ = (l2_plen << 2) | 1;
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si5 = (struct gsm48_system_information_type_5 *) output;
+
+	/* l2 pseudo length, not part of msg: 18 */
+	si5->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si5->skip_indicator = 0;
+	si5->system_information = GSM48_MT_RR_SYSINFO_5;
+	rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, 1);
+	if (rc < 0)
+		return rc;
+
+	/* 04.08 9.1.37: L2 Pseudo Length of 18 */
+	return l2_plen;
+}
+
+static int generate_si6(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_6 *si6;
+	int l2_plen = 11;
+
+	memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	/* ip.access nanoBTS needs l2_plen!! */
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+	case GSM_BTS_TYPE_HSL_FEMTO:
+		*output++ = (l2_plen << 2) | 1;
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si6 = (struct gsm48_system_information_type_6 *) output;
+
+	/* l2 pseudo length, not part of msg: 11 */
+	si6->rr_protocol_discriminator = GSM48_PDISC_RR;
+	si6->skip_indicator = 0;
+	si6->system_information = GSM48_MT_RR_SYSINFO_6;
+	si6->cell_identity = htons(bts->cell_identity);
+	gsm48_generate_lai(&si6->lai, bts->network->country_code,
+			   bts->network->network_code,
+			   bts->location_area_code);
+	si6->cell_options = bts->si_common.cell_options;
+	si6->ncc_permitted = bts->si_common.ncc_permitted;
+
+	/* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */
+
+	return l2_plen;
+}
+
+static struct gsm48_si13_info si13_default = {
+	.cell_opts = {
+		.nmo 		= GPRS_NMO_II,
+		.t3168		= 2000,
+		.t3192		= 200,
+		.drx_timer_max	= 3,
+		.bs_cv_max	= 15,
+		.ext_info_present = 1,
+		.ext_info = {
+			/* The values below are just guesses ! */
+			.egprs_supported = 0,
+			.use_egprs_p_ch_req = 1,
+			.bep_period = 5,
+			.pfc_supported = 0,
+			.dtm_supported = 0,
+			.bss_paging_coordination = 0,
+		},
+	},
+	.pwr_ctrl_pars = {
+		.alpha		= 10,	/* a = 1.0 */
+		.t_avg_w	= 16,
+		.t_avg_t	= 16,
+		.pc_meas_chan	= 0, 	/* downling measured on CCCH */
+		.n_avg_i	= 8,
+	},
+	.bcch_change_mark	= 1,
+	.si_change_field	= 0,
+	.pbcch_present		= 0,
+	{
+		.no_pbcch = {
+			.rac		= 0,	/* needs to be patched */
+			.spgc_ccch_sup 	= 0,
+			.net_ctrl_ord	= 0,
+			.prio_acc_thr	= 6,
+		},
+	},
+};
+
+static int generate_si13(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_13 *si13 =
+		(struct gsm48_system_information_type_13 *) output;
+	int ret;
+
+	memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si13->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+	si13->header.skip_indicator = 0;
+	si13->header.system_information = GSM48_MT_RR_SYSINFO_13;
+
+	si13_default.no_pbcch.rac = bts->gprs.rac;
+
+	ret = rest_octets_si13(si13->rest_octets, &si13_default);
+	if (ret < 0)
+		return ret;
+
+	/* length is coded in bit 2 an up */
+	si13->header.l2_plen = 0x01;
+
+	return sizeof (*si13) + ret;
+}
+
+static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = {
+	[SYSINFO_TYPE_1]	= RSL_SYSTEM_INFO_1,
+	[SYSINFO_TYPE_2]	= RSL_SYSTEM_INFO_2,
+	[SYSINFO_TYPE_3]	= RSL_SYSTEM_INFO_3,
+	[SYSINFO_TYPE_4]	= RSL_SYSTEM_INFO_4,
+	[SYSINFO_TYPE_5]	= RSL_SYSTEM_INFO_5,
+	[SYSINFO_TYPE_6]	= RSL_SYSTEM_INFO_6,
+	[SYSINFO_TYPE_7]	= RSL_SYSTEM_INFO_7,
+	[SYSINFO_TYPE_8]	= RSL_SYSTEM_INFO_8,
+	[SYSINFO_TYPE_9]	= RSL_SYSTEM_INFO_9,
+	[SYSINFO_TYPE_10]	= RSL_SYSTEM_INFO_10,
+	[SYSINFO_TYPE_13]	= RSL_SYSTEM_INFO_13,
+	[SYSINFO_TYPE_16]	= RSL_SYSTEM_INFO_16,
+	[SYSINFO_TYPE_17]	= RSL_SYSTEM_INFO_17,
+	[SYSINFO_TYPE_18]	= RSL_SYSTEM_INFO_18,
+	[SYSINFO_TYPE_19]	= RSL_SYSTEM_INFO_19,
+	[SYSINFO_TYPE_20]	= RSL_SYSTEM_INFO_20,
+	[SYSINFO_TYPE_2bis]	= RSL_SYSTEM_INFO_2bis,
+	[SYSINFO_TYPE_2ter]	= RSL_SYSTEM_INFO_2ter,
+	[SYSINFO_TYPE_2quater]	= RSL_SYSTEM_INFO_2quater,
+	[SYSINFO_TYPE_5bis]	= RSL_SYSTEM_INFO_5bis,
+	[SYSINFO_TYPE_5ter]	= RSL_SYSTEM_INFO_5ter,
+};
+
+static const uint8_t rsl2sitype[0xff] = {
+	[RSL_SYSTEM_INFO_1] = SYSINFO_TYPE_1,
+	[RSL_SYSTEM_INFO_2] = SYSINFO_TYPE_2,
+	[RSL_SYSTEM_INFO_3] = SYSINFO_TYPE_3,
+	[RSL_SYSTEM_INFO_4] = SYSINFO_TYPE_4,
+	[RSL_SYSTEM_INFO_5] = SYSINFO_TYPE_5,
+	[RSL_SYSTEM_INFO_6] = SYSINFO_TYPE_6,
+	[RSL_SYSTEM_INFO_7] = SYSINFO_TYPE_7,
+	[RSL_SYSTEM_INFO_8] = SYSINFO_TYPE_8,
+	[RSL_SYSTEM_INFO_9] = SYSINFO_TYPE_9,
+	[RSL_SYSTEM_INFO_10] = SYSINFO_TYPE_10,
+	[RSL_SYSTEM_INFO_13] = SYSINFO_TYPE_13,
+	[RSL_SYSTEM_INFO_16] = SYSINFO_TYPE_16,
+	[RSL_SYSTEM_INFO_17] = SYSINFO_TYPE_17,
+	[RSL_SYSTEM_INFO_18] = SYSINFO_TYPE_18,
+	[RSL_SYSTEM_INFO_19] = SYSINFO_TYPE_19,
+	[RSL_SYSTEM_INFO_20] = SYSINFO_TYPE_20,
+	[RSL_SYSTEM_INFO_2bis] = SYSINFO_TYPE_2bis,
+	[RSL_SYSTEM_INFO_2ter] = SYSINFO_TYPE_2ter,
+	[RSL_SYSTEM_INFO_2quater] = SYSINFO_TYPE_2quater,
+	[RSL_SYSTEM_INFO_5bis] = SYSINFO_TYPE_5bis,
+	[RSL_SYSTEM_INFO_5ter] = SYSINFO_TYPE_5ter,
+};
+
+typedef int (*gen_si_fn_t)(uint8_t *output, struct gsm_bts *bts);
+
+static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = {
+	[SYSINFO_TYPE_1] = &generate_si1,
+	[SYSINFO_TYPE_2] = &generate_si2,
+	[SYSINFO_TYPE_3] = &generate_si3,
+	[SYSINFO_TYPE_4] = &generate_si4,
+	[SYSINFO_TYPE_5] = &generate_si5,
+	[SYSINFO_TYPE_6] = &generate_si6,
+	[SYSINFO_TYPE_13] = &generate_si13,
+};
+
+const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE] = {
+	{ SYSINFO_TYPE_1,	"1" },
+	{ SYSINFO_TYPE_2,	"2" },
+	{ SYSINFO_TYPE_3,	"3" },
+	{ SYSINFO_TYPE_4,	"4" },
+	{ SYSINFO_TYPE_5,	"5" },
+	{ SYSINFO_TYPE_6,	"6" },
+	{ SYSINFO_TYPE_7,	"7" },
+	{ SYSINFO_TYPE_8,	"8" },
+	{ SYSINFO_TYPE_9,	"9" },
+	{ SYSINFO_TYPE_10,	"10" },
+	{ SYSINFO_TYPE_13,	"13" },
+	{ SYSINFO_TYPE_16,	"16" },
+	{ SYSINFO_TYPE_17,	"17" },
+	{ SYSINFO_TYPE_18,	"18" },
+	{ SYSINFO_TYPE_19,	"19" },
+	{ SYSINFO_TYPE_20,	"20" },
+	{ SYSINFO_TYPE_2bis,	"2bis" },
+	{ SYSINFO_TYPE_2ter,	"2ter" },
+	{ SYSINFO_TYPE_2quater,	"2quater" },
+	{ SYSINFO_TYPE_5bis,	"5bis" },
+	{ SYSINFO_TYPE_5ter,	"5ter" },
+	{ 0, NULL }
+};
+
+uint8_t gsm_sitype2rsl(enum osmo_sysinfo_type si_type)
+{
+	return sitype2rsl[si_type];
+}
+
+const char *gsm_sitype_name(enum osmo_sysinfo_type si_type)
+{
+	return get_value_string(osmo_sitype_strs, si_type);
+}
+
+int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type)
+{
+	gen_si_fn_t gen_si;
+
+	switch (bts->gprs.mode) {
+	case BTS_GPRS_EGPRS:
+		si13_default.cell_opts.ext_info_present = 1;
+		si13_default.cell_opts.ext_info.egprs_supported = 1;
+		/* fallthrough */
+	case BTS_GPRS_GPRS:
+		si_info.gprs_ind.present = 1;
+		break;
+	case BTS_GPRS_NONE:
+		si_info.gprs_ind.present = 0;
+		break;
+	}
+
+	memcpy(&si_info.selection_params,
+	       &bts->si_common.cell_ro_sel_par,
+	       sizeof(struct gsm48_si_selection_params));
+
+	gen_si = gen_si_fn[si_type];
+	if (!gen_si)
+		return -EINVAL;
+
+	return gen_si(bts->si_buf[si_type], bts);
+}
diff --git a/src/libbsc/transaction.c b/src/libbsc/transaction.c
new file mode 100644
index 0000000..9b4af1a
--- /dev/null
+++ b/src/libbsc/transaction.c
@@ -0,0 +1,152 @@
+/* GSM 04.07 Transaction handling */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/transaction.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mncc.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mncc.h>
+#include <openbsc/paging.h>
+#include <openbsc/osmo_msc.h>
+
+void *tall_trans_ctx;
+
+void _gsm48_cc_trans_free(struct gsm_trans *trans);
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr,
+				   u_int8_t proto, u_int8_t trans_id)
+{
+	struct gsm_trans *trans;
+	struct gsm_network *net = subscr->net;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr == subscr &&
+		    trans->protocol == proto &&
+		    trans->transaction_id == trans_id)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					u_int32_t callref)
+{
+	struct gsm_trans *trans;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->callref == callref)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr,
+			      u_int8_t protocol, u_int8_t trans_id,
+			      u_int32_t callref)
+{
+	struct gsm_trans *trans;
+
+	DEBUGP(DCC, "subscr=%p, subscr->net=%p\n", subscr, subscr->net);
+
+	trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
+	if (!trans)
+		return NULL;
+
+	trans->subscr = subscr;
+	subscr_get(trans->subscr);
+
+	trans->protocol = protocol;
+	trans->transaction_id = trans_id;
+	trans->callref = callref;
+
+	llist_add_tail(&trans->entry, &subscr->net->trans_list);
+
+	return trans;
+}
+
+void trans_free(struct gsm_trans *trans)
+{
+	switch (trans->protocol) {
+	case GSM48_PDISC_CC:
+		_gsm48_cc_trans_free(trans);
+		break;
+	case GSM48_PDISC_SMS:
+		_gsm411_sms_trans_free(trans);
+		break;
+	}
+
+	/* FIXME: implement a sane way to stop this. */
+	if (!trans->conn && trans->paging_request) {
+		LOGP(DNM, LOGL_ERROR,
+		     "Transaction freed while paging for sub: %llu\n",
+		     trans->subscr->id);
+		trans->paging_request = NULL;
+	}
+
+	if (trans->subscr)
+		subscr_put(trans->subscr);
+
+	llist_del(&trans->entry);
+
+	if (trans->conn)
+		msc_release_connection(trans->conn);
+
+
+	talloc_free(trans);
+}
+
+/* allocate an unused transaction ID for the given subscriber
+ * in the given protocol using the ti_flag specified */
+int trans_assign_trans_id(struct gsm_subscriber *subscr,
+			  u_int8_t protocol, u_int8_t ti_flag)
+{
+	struct gsm_network *net = subscr->net;
+	struct gsm_trans *trans;
+	unsigned int used_tid_bitmask = 0;
+	int i, j, h;
+
+	if (ti_flag)
+		ti_flag = 0x8;
+
+	/* generate bitmask of already-used TIDs for this (subscr,proto) */
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr != subscr ||
+		    trans->protocol != protocol ||
+		    trans->transaction_id == 0xff)
+			continue;
+		used_tid_bitmask |= (1 << trans->transaction_id);
+	}
+
+	/* find a new one, trying to go in a 'circular' pattern */
+	for (h = 6; h > 0; h--)
+		if (used_tid_bitmask & (1 << (h | ti_flag)))
+			break;
+	for (i = 0; i < 7; i++) {
+		j = ((h + i) % 7) | ti_flag;
+		if ((used_tid_bitmask & (1 << j)) == 0)
+			return j;
+	}
+
+	return -1;
+}
+
diff --git a/src/libcommon/Makefile.am b/src/libcommon/Makefile.am
new file mode 100644
index 0000000..2895452
--- /dev/null
+++ b/src/libcommon/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libcommon.a
+
+libcommon_a_SOURCES = bsc_version.c common_vty.c debug.c gsm_data.c socket.c talloc_ctx.c
diff --git a/src/libcommon/Makefile.in b/src/libcommon/Makefile.in
new file mode 100644
index 0000000..41d52fc
--- /dev/null
+++ b/src/libcommon/Makefile.in
@@ -0,0 +1,457 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libcommon
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libcommon_a_AR = $(AR) $(ARFLAGS)
+libcommon_a_LIBADD =
+am_libcommon_a_OBJECTS = bsc_version.$(OBJEXT) common_vty.$(OBJEXT) \
+	debug.$(OBJEXT) gsm_data.$(OBJEXT) socket.$(OBJEXT) \
+	talloc_ctx.$(OBJEXT)
+libcommon_a_OBJECTS = $(am_libcommon_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libcommon_a_SOURCES)
+DIST_SOURCES = $(libcommon_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libcommon.a
+libcommon_a_SOURCES = bsc_version.c common_vty.c debug.c gsm_data.c socket.c talloc_ctx.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libcommon/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libcommon/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libcommon.a: $(libcommon_a_OBJECTS) $(libcommon_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libcommon.a
+	$(AM_V_AR)$(libcommon_a_AR) libcommon.a $(libcommon_a_OBJECTS) $(libcommon_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libcommon.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_version.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_data.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socket.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/talloc_ctx.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libcommon/bsc_version.c b/src/libcommon/bsc_version.c
new file mode 100644
index 0000000..be5d28c
--- /dev/null
+++ b/src/libcommon/bsc_version.c
@@ -0,0 +1,30 @@
+/* Hold the copyright and version string */
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../bscconfig.h"
+
+const char *openbsc_copyright =
+	"Copyright (C) 2008-2010 Harald Welte, Holger Freyther\r\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+	"Dieter Spaar, Andreas Eversberg, Sylvain Munaut\r\n\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+
diff --git a/src/libcommon/common_vty.c b/src/libcommon/common_vty.c
new file mode 100644
index 0000000..84375a2
--- /dev/null
+++ b/src/libcommon/common_vty.c
@@ -0,0 +1,225 @@
+/* OpenBSC VTY common helpers */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_nat.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+
+enum node_type bsc_vty_go_parent(struct vty *vty)
+{
+	switch (vty->node) {
+	case GSMNET_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case BTS_NODE:
+		vty->node = GSMNET_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts *bts = vty->index;
+			vty->index = bts->network;
+		}
+		break;
+	case TRX_NODE:
+		vty->node = BTS_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx *trx = vty->index;
+			vty->index = trx->bts;
+		}
+		break;
+	case TS_NODE:
+		vty->node = TRX_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx_ts *ts = vty->index;
+			vty->index = ts->trx;
+		}
+		break;
+	case OML_NODE:
+	case OM2K_NODE:
+		vty->node = ENABLE_NODE;
+		talloc_free(vty->index);
+		vty->index = NULL;
+		break;
+	case NAT_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case NAT_BSC_NODE:
+		vty->node = NAT_NODE;
+		{
+			struct bsc_config *bsc_config = vty->index;
+			vty->index = bsc_config->nat;
+		}
+		break;
+	case MSC_NODE:
+		vty->node = GSMNET_NODE;
+		break;
+	case TRUNK_NODE:
+		vty->node = MGCP_NODE;
+		break;
+	default:
+		vty->node = CONFIG_NODE;
+	}
+
+	return vty->node;
+}
+
+/* Down vty node level. */
+gDEFUN(ournode_exit,
+       ournode_exit_cmd, "exit", "Exit current mode and down to previous mode\n")
+{
+	switch (vty->node) {
+	case GSMNET_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case BTS_NODE:
+		vty->node = GSMNET_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts *bts = vty->index;
+			vty->index = bts->network;
+			vty->index_sub = NULL;
+		}
+		break;
+	case TRX_NODE:
+		vty->node = BTS_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx *trx = vty->index;
+			vty->index = trx->bts;
+			vty->index_sub = &trx->bts->description;
+		}
+		break;
+	case TS_NODE:
+		vty->node = TRX_NODE;
+		{
+			/* set vty->index correctly ! */
+			struct gsm_bts_trx_ts *ts = vty->index;
+			vty->index = ts->trx;
+			vty->index_sub = &ts->trx->description;
+		}
+		break;
+	case NAT_BSC_NODE:
+		vty->node = NAT_NODE;
+		{
+			struct bsc_config *bsc_config = vty->index;
+			vty->index = bsc_config->nat;
+		}
+		break;
+	case MGCP_NODE:
+	case GBPROXY_NODE:
+	case SGSN_NODE:
+	case NS_NODE:
+	case BSSGP_NODE:
+	case NAT_NODE:
+		vty->node = CONFIG_NODE;
+		vty->index = NULL;
+		break;
+	case OML_NODE:
+	case OM2K_NODE:
+		vty->node = ENABLE_NODE;
+		talloc_free(vty->index);
+		vty->index = NULL;
+		break;
+	case MSC_NODE:
+		vty->node = GSMNET_NODE;
+		break;
+	case TRUNK_NODE:
+		vty->node = MGCP_NODE;
+		vty->index = NULL;
+		break;
+	default:
+		break;
+	}
+	return CMD_SUCCESS;
+}
+
+/* End of configuration. */
+gDEFUN(ournode_end,
+       ournode_end_cmd, "end", "End current mode and change to enable mode.")
+{
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CONFIG_NODE:
+	case GSMNET_NODE:
+	case BTS_NODE:
+	case TRX_NODE:
+	case TS_NODE:
+	case MGCP_NODE:
+	case TRUNK_NODE:
+	case GBPROXY_NODE:
+	case SGSN_NODE:
+	case NS_NODE:
+	case VTY_NODE:
+	case NAT_NODE:
+	case NAT_BSC_NODE:
+	case MSC_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		vty->index = NULL;
+		vty->index_sub = NULL;
+		break;
+	default:
+		break;
+	}
+	return CMD_SUCCESS;
+}
+
+int bsc_vty_is_config_node(struct vty *vty, int node)
+{
+	switch (node) {
+	/* add items that are not config */
+	case OML_NODE:
+	case OM2K_NODE:
+	case SUBSCR_NODE:
+	case CONFIG_NODE:
+		return 0;
+
+	default:
+		return 1;
+	}
+}
+
+/* a talloc string replace routine */
+void bsc_replace_string(void *ctx, char **dst, const char *newstr)
+{
+	if (*dst)
+		talloc_free(*dst);
+	*dst = talloc_strdup(ctx, newstr);
+}
diff --git a/src/libcommon/debug.c b/src/libcommon/debug.c
new file mode 100644
index 0000000..ea5db49
--- /dev/null
+++ b/src/libcommon/debug.c
@@ -0,0 +1,249 @@
+/* OpenBSC Debugging/Logging support code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <errno.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+#include <osmocore/logging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+/* default categories */
+static const struct log_info_cat default_categories[] = {
+	[DRLL] = {
+		.name = "DRLL",
+		.description = "A-bis Radio Link Layer (RLL)",
+		.color = "\033[1;31m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DCC] = {
+		.name = "DCC",
+		.description = "Layer3 Call Control (CC)",
+		.color = "\033[1;32m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMM] = {
+		.name = "DMM",
+		.description = "Layer3 Mobility Management (MM)",
+		.color = "\033[1;33m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DRR] = {
+		.name = "DRR",
+		.description = "Layer3 Radio Resource (RR)",
+		.color = "\033[1;34m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DRSL] = {
+		.name = "DRSL",
+		.description = "A-bis Radio Siganlling Link (RSL)",
+		.color = "\033[1;35m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DNM] =	{
+		.name = "DNM",
+		.description = "A-bis Network Management / O&M (NM/OML)",
+		.color = "\033[1;36m",
+		.enabled = 1, .loglevel = LOGL_INFO,
+	},
+	[DMNCC] = {
+		.name = "DMNCC",
+		.description = "MNCC API for Call Control application",
+		.color = "\033[1;39m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DSMS] = {
+		.name = "DSMS",
+		.description = "Layer3 Short Message Service (SMS)",
+		.color = "\033[1;37m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DPAG]	= {
+		.name = "DPAG",
+		.description = "Paging Subsystem",
+		.color = "\033[1;38m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMEAS] = {
+		.name = "DMEAS",
+		.description = "Radio Measurement Processing",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DMI] = {
+		.name = "DMI",
+		.description = "A-bis Input Driver for Signalling",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DMIB] = {
+		.name = "DMIB",
+		.description = "A-bis Input Driver for B-Channels (voice)",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DMUX] = {
+		.name = "DMUX",
+		.description = "A-bis B-Subchannel TRAU Frame Multiplex",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DINP] = {
+		.name = "DINP",
+		.description = "A-bis Intput Subsystem",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DSCCP] = {
+		.name = "DSCCP",
+		.description = "SCCP Protocol",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMSC] = {
+		.name = "DMSC",
+		.description = "Mobile Switching Center",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DMGCP] = {
+		.name = "DMGCP",
+		.description = "Media Gateway Control Protocol",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DHO] = {
+		.name = "DHO",
+		.description = "Hand-Over",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DDB] = {
+		.name = "DDB",
+		.description = "Database Layer",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+	[DREF] = {
+		.name = "DREF",
+		.description = "Reference Counting",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[DGPRS] = {
+		.name = "DGPRS",
+		.description = "GPRS Packet Service",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DNS] = {
+		.name = "DNS",
+		.description = "GPRS Network Service (NS)",
+		.enabled = 1, .loglevel = LOGL_INFO,
+	},
+	[DBSSGP] = {
+		.name = "DBSSGP",
+		.description = "GPRS BSS Gateway Protocol (BSSGP)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DLLC] = {
+		.name = "DLLC",
+		.description = "GPRS Logical Link Control Protocol (LLC)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DSNDCP] = {
+		.name = "DSNDCP",
+		.description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+	[DNAT] = {
+		.name = "DNAT",
+		.description = "GSM 08.08 NAT/Multipkexer",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+	},
+};
+
+enum log_filter {
+	_FLT_ALL = LOG_FILTER_ALL,	/* libosmocore */
+	FLT_IMSI = 1,
+	FLT_NSVC = 2,
+	FLT_BVC  = 3,
+};
+
+static int filter_fn(const struct log_context *ctx,
+		     struct log_target *tar)
+{
+	struct gsm_subscriber *subscr = ctx->ctx[BSC_CTX_SUBSCR];
+	const struct gprs_nsvc *nsvc = ctx->ctx[BSC_CTX_NSVC];
+	const struct gprs_nsvc *bvc = ctx->ctx[BSC_CTX_BVC];
+
+	if ((tar->filter_map & (1 << FLT_IMSI)) != 0
+	    && subscr && strcmp(subscr->imsi, tar->filter_data[FLT_IMSI]) == 0)
+		return 1;
+
+	/* Filter on the NS Virtual Connection */
+	if ((tar->filter_map & (1 << FLT_NSVC)) != 0
+	    && nsvc && (nsvc == tar->filter_data[FLT_NSVC]))
+		return 1;
+
+	/* Filter on the NS Virtual Connection */
+	if ((tar->filter_map & (1 << FLT_BVC)) != 0
+	    && bvc && (bvc == tar->filter_data[FLT_BVC]))
+		return 1;
+
+	return 0;
+}
+
+const struct log_info log_info = {
+	.filter_fn = filter_fn,
+	.cat = default_categories,
+	.num_cat = ARRAY_SIZE(default_categories),
+};
+
+void log_set_imsi_filter(struct log_target *target, const char *imsi)
+{
+	if (imsi) {
+		target->filter_map |= (1 << FLT_IMSI);
+		target->filter_data[FLT_IMSI] = talloc_strdup(target, imsi);
+	} else if (target->filter_data[FLT_IMSI]) {
+		target->filter_map &= ~(1 << FLT_IMSI);
+		talloc_free(target->filter_data[FLT_IMSI]);
+		target->filter_data[FLT_IMSI] = NULL;
+	}
+}
+
+void log_set_nsvc_filter(struct log_target *target, struct gprs_nsvc *nsvc)
+{
+	if (nsvc) {
+		target->filter_map |= (1 << FLT_NSVC);
+		target->filter_data[FLT_NSVC] = nsvc;
+	} else if (target->filter_data[FLT_NSVC]) {
+		target->filter_map = ~(1 << FLT_NSVC);
+		target->filter_data[FLT_NSVC] = NULL;
+	}
+}
+
+void log_set_bvc_filter(struct log_target *target, struct bssgp_bvc_ctx *bctx)
+{
+	if (bctx) {
+		target->filter_map |= (1 << FLT_BVC);
+		target->filter_data[FLT_BVC] = bctx;
+	} else if (target->filter_data[FLT_NSVC]) {
+		target->filter_map = ~(1 << FLT_BVC);
+		target->filter_data[FLT_BVC] = NULL;
+	}
+}
diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c
new file mode 100644
index 0000000..b2c52e4
--- /dev/null
+++ b/src/libcommon/gsm_data.c
@@ -0,0 +1,592 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/statistics.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/abis_nm.h>
+
+void *tall_bsc_ctx;
+
+static LLIST_HEAD(bts_models);
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, u_int8_t e1_nr,
+		   u_int8_t e1_ts, u_int8_t e1_ts_ss)
+{
+	ts->e1_link.e1_nr = e1_nr;
+	ts->e1_link.e1_ts = e1_ts;
+	ts->e1_link.e1_ts_ss = e1_ts_ss;
+}
+
+static const struct value_string pchan_names[] = {
+	{ GSM_PCHAN_NONE,	"NONE" },
+	{ GSM_PCHAN_CCCH,	"CCCH" },
+	{ GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" },
+	{ GSM_PCHAN_TCH_F,	"TCH/F" },
+	{ GSM_PCHAN_TCH_H,	"TCH/H" },
+	{ GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+	{ GSM_PCHAN_PDCH,	"PDCH" },
+	{ GSM_PCHAN_TCH_F_PDCH,	"TCH/F_PDCH" },
+	{ GSM_PCHAN_UNKNOWN,	"UNKNOWN" },
+	{ 0,			NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+	return get_value_string(pchan_names, c);
+}
+
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
+{
+	return get_string_value(pchan_names, name);
+}
+
+static const struct value_string lchant_names[] = {
+	{ GSM_LCHAN_NONE,	"NONE" },
+	{ GSM_LCHAN_SDCCH,	"SDCCH" },
+	{ GSM_LCHAN_TCH_F,	"TCH/F" },
+	{ GSM_LCHAN_TCH_H,	"TCH/H" },
+	{ GSM_LCHAN_UNKNOWN,	"UNKNOWN" },
+	{ 0,			NULL }
+};
+
+const char *gsm_lchant_name(enum gsm_chan_t c)
+{
+	return get_value_string(lchant_names, c);
+}
+
+static const struct value_string lchan_s_names[] = {
+	{ LCHAN_S_NONE,		"NONE" },
+	{ LCHAN_S_ACT_REQ,	"ACTIVATION REQUESTED" },
+	{ LCHAN_S_ACTIVE,	"ACTIVE" },
+	{ LCHAN_S_INACTIVE,	"INACTIVE" },
+	{ LCHAN_S_REL_REQ,	"RELEASE REQUESTED" },
+	{ LCHAN_S_REL_ERR,	"RELEASE DUE ERROR" },
+	{ 0,			NULL }
+};
+
+const char *gsm_lchans_name(enum gsm_lchan_state s)
+{
+	return get_value_string(lchan_s_names, s);
+}
+
+static const struct value_string chreq_names[] = {
+	{ GSM_CHREQ_REASON_EMERG,	"EMERGENCY" },
+	{ GSM_CHREQ_REASON_PAG,		"PAGING" },
+	{ GSM_CHREQ_REASON_CALL,	"CALL" },
+	{ GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" },
+	{ GSM_CHREQ_REASON_OTHER,	"OTHER" },
+	{ 0,				NULL }
+};
+
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c)
+{
+	return get_value_string(chreq_names, c);
+}
+
+static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
+{
+	struct gsm_bts_model *model;
+
+	llist_for_each_entry(model, &bts_models, list) {
+		if (model->type == type)
+			return model;
+	}
+
+	return NULL;
+}
+
+int gsm_bts_model_register(struct gsm_bts_model *model)
+{
+	if (bts_model_find(model->type))
+		return -EEXIST;
+
+	tlv_def_patch(&model->nm_att_tlvdef, &nm_att_tlvdef);
+	llist_add_tail(&model->list, &bts_models);
+	return 0;
+}
+
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
+	int k;
+
+	if (!trx)
+		return NULL;
+
+	trx->bts = bts;
+	trx->nr = bts->num_trx++;
+	trx->nm_state.administrative = NM_STATE_UNLOCKED;
+
+	for (k = 0; k < TRX_NR_TS; k++) {
+		struct gsm_bts_trx_ts *ts = &trx->ts[k];
+		int l;
+		
+		ts->trx = trx;
+		ts->nr = k;
+		ts->pchan = GSM_PCHAN_NONE;
+
+		ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
+		ts->hopping.arfcns.data = ts->hopping.arfcns_data;
+		ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
+		ts->hopping.ma.data = ts->hopping.ma_data;
+
+		for (l = 0; l < TS_MAX_LCHAN; l++) {
+			struct gsm_lchan *lchan;
+			lchan = &ts->lchan[l];
+
+			lchan->ts = ts;
+			lchan->nr = l;
+			lchan->type = GSM_LCHAN_NONE;
+		}
+	}
+
+	if (trx->nr != 0)
+		trx->nominal_power = bts->c0->nominal_power;
+
+	llist_add_tail(&trx->list, &bts->trx_list);
+
+	return trx;
+}
+
+static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+static const uint8_t bts_cell_timer_default[] =
+				{ 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 };
+
+struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, enum gsm_bts_type type,
+			      u_int8_t tsc, u_int8_t bsic)
+{
+	struct gsm_bts *bts = talloc_zero(net, struct gsm_bts);
+	struct gsm_bts_model *model = bts_model_find(type);
+	int i;
+
+	if (!bts)
+		return NULL;
+
+	if (!model && type != GSM_BTS_TYPE_UNKNOWN) {
+		talloc_free(bts);
+		return NULL;
+	}
+
+	bts->network = net;
+	bts->nr = net->num_bts++;
+	bts->type = type;
+	bts->model = model;
+	bts->tsc = tsc;
+	bts->bsic = bsic;
+	bts->num_trx = 0;
+	INIT_LLIST_HEAD(&bts->trx_list);
+	bts->ms_max_power = 15;	/* dBm */
+
+	bts->neigh_list_manual_mode = 0;
+	bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
+	bts->si_common.cell_sel_par.rxlev_acc_min = 0;
+	bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
+	bts->si_common.neigh_list.data_len =
+				sizeof(bts->si_common.data.neigh_list);
+	bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list;
+	bts->si_common.si5_neigh_list.data_len =
+				sizeof(bts->si_common.data.si5_neigh_list);
+	bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc;
+	bts->si_common.cell_alloc.data_len =
+				sizeof(bts->si_common.data.cell_alloc);
+	bts->si_common.rach_control.re = 1; /* no re-establishment */
+	bts->si_common.rach_control.tx_integer = 9;  /* 12 slots spread - 217/115 slots delay */
+	bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */
+	bts->si_common.rach_control.t2 = 4; /* no emergency calls */
+
+	for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+		bts->gprs.nsvc[i].bts = bts;
+		bts->gprs.nsvc[i].id = i;
+	}
+	memcpy(&bts->gprs.nse.timer, bts_nse_timer_default,
+		sizeof(bts->gprs.nse.timer));
+	memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
+		sizeof(bts->gprs.cell.timer));
+
+	/* create our primary TRX */
+	bts->c0 = gsm_bts_trx_alloc(bts);
+	if (!bts->c0) {
+		talloc_free(bts);
+		return NULL;
+	}
+	bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4;
+
+	bts->rach_b_thresh = -1;
+	bts->rach_ldavg_slots = -1;
+	bts->paging.free_chans_need = -1;
+	INIT_LLIST_HEAD(&bts->abis_queue);
+
+	llist_add_tail(&bts->list, &net->bts_list);
+
+	return bts;
+}
+
+struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_code,
+				     int (*mncc_recv)(struct gsm_network *, struct msgb *))
+{
+	struct gsm_network *net;
+
+	net = talloc_zero(tall_bsc_ctx, struct gsm_network);
+	if (!net)
+		return NULL;
+
+	net->msc_data = talloc_zero(net, struct osmo_msc_data);
+	if (!net->msc_data) {
+		talloc_free(net);
+		return NULL;
+	}
+
+	net->country_code = country_code;
+	net->network_code = network_code;
+	net->num_bts = 0;
+	net->reject_cause = GSM48_REJECT_ROAMING_NOT_ALLOWED;
+	net->T3101 = GSM_T3101_DEFAULT;
+	net->T3113 = GSM_T3113_DEFAULT;
+	/* FIXME: initialize all other timers! */
+
+	/* default set of handover parameters */
+	net->handover.win_rxlev_avg = 10;
+	net->handover.win_rxqual_avg = 1;
+	net->handover.win_rxlev_avg_neigh = 10;
+	net->handover.pwr_interval = 6;
+	net->handover.pwr_hysteresis = 3;
+	net->handover.max_distance = 9999;
+
+	INIT_LLIST_HEAD(&net->trans_list);
+	INIT_LLIST_HEAD(&net->upqueue);
+	INIT_LLIST_HEAD(&net->bts_list);
+
+	net->stats.chreq.total = counter_alloc("net.chreq.total");
+	net->stats.chreq.no_channel = counter_alloc("net.chreq.no_channel");
+	net->stats.handover.attempted = counter_alloc("net.handover.attempted");
+	net->stats.handover.no_channel = counter_alloc("net.handover.no_channel");
+	net->stats.handover.timeout = counter_alloc("net.handover.timeout");
+	net->stats.handover.completed = counter_alloc("net.handover.completed");
+	net->stats.handover.failed = counter_alloc("net.handover.failed");
+	net->stats.loc_upd_type.attach = counter_alloc("net.loc_upd_type.attach");
+	net->stats.loc_upd_type.normal = counter_alloc("net.loc_upd_type.normal");
+	net->stats.loc_upd_type.periodic = counter_alloc("net.loc_upd_type.periodic");
+	net->stats.loc_upd_type.detach = counter_alloc("net.imsi_detach.count");
+	net->stats.loc_upd_resp.reject = counter_alloc("net.loc_upd_resp.reject");
+	net->stats.loc_upd_resp.accept = counter_alloc("net.loc_upd_resp.accept");
+	net->stats.paging.attempted = counter_alloc("net.paging.attempted");
+	net->stats.paging.detached = counter_alloc("net.paging.detached");
+	net->stats.paging.completed = counter_alloc("net.paging.completed");
+	net->stats.paging.expired = counter_alloc("net.paging.expired");
+	net->stats.sms.submitted = counter_alloc("net.sms.submitted");
+	net->stats.sms.no_receiver = counter_alloc("net.sms.no_receiver");
+	net->stats.sms.delivered = counter_alloc("net.sms.delivered");
+	net->stats.sms.rp_err_mem = counter_alloc("net.sms.rp_err_mem");
+	net->stats.sms.rp_err_other = counter_alloc("net.sms.rp_err_other");
+	net->stats.call.mo_setup = counter_alloc("net.call.mo_setup");
+	net->stats.call.mo_connect_ack = counter_alloc("net.call.mo_connect_ack");
+	net->stats.call.mt_setup = counter_alloc("net.call.mt_setup");
+	net->stats.call.mt_connect = counter_alloc("net.call.mt_connect");
+	net->stats.chan.rf_fail = counter_alloc("net.chan.rf_fail");
+	net->stats.chan.rll_err = counter_alloc("net.chan.rll_err");
+	net->stats.bts.oml_fail = counter_alloc("net.bts.oml_fail");
+	net->stats.bts.rsl_fail = counter_alloc("net.bts.rsl_fail");
+
+	net->mncc_recv = mncc_recv;
+
+	net->msc_data->msc_ip = talloc_strdup(net, "127.0.0.1");
+	net->msc_data->msc_port = 5000;
+	net->msc_data->ping_timeout = 20;
+	net->msc_data->pong_timeout = 5;
+	net->msc_data->core_ncc = -1;
+	net->msc_data->core_mcc = -1;
+	net->msc_data->rtp_base = 4000;
+
+	gsm_net_update_ctype(net);
+
+	return net;
+}
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num)
+{
+	struct gsm_bts *bts;
+
+	if (num >= net->num_bts)
+		return NULL;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (bts->nr == num)
+			return bts;
+	}
+
+	return NULL;
+}
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+				 u_int16_t arfcn, u_int8_t bsic)
+{
+	struct gsm_bts *neigh;
+	/* FIXME: use some better heuristics here to determine which cell
+	 * using this ARFCN really is closest to the target cell.  For
+	 * now we simply assume that each ARFCN will only be used by one
+	 * cell */
+
+	llist_for_each_entry(neigh, &bts->network->bts_list, list) {
+		if (neigh->c0->arfcn == arfcn &&
+		    neigh->bsic == bsic)
+			return neigh;
+	}
+
+	return NULL;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_num(struct gsm_bts *bts, int num)
+{
+	struct gsm_bts_trx *trx;
+
+	if (num >= bts->num_trx)
+		return NULL;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->nr == num)
+			return trx;
+	}
+
+	return NULL;
+}
+
+static char ts2str[255];
+
+char *gsm_trx_name(struct gsm_bts_trx *trx)
+{
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
+		 trx->bts->nr, trx->nr);
+
+	return ts2str;
+}
+
+
+char *gsm_ts_name(struct gsm_bts_trx_ts *ts)
+{
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)",
+		 ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	return ts2str;
+}
+
+char *gsm_lchan_name(struct gsm_lchan *lchan)
+{
+	struct gsm_bts_trx_ts *ts = lchan->ts;
+
+	snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
+		 ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
+
+	return ts2str;
+}
+
+static const struct value_string bts_types[] = {
+	{ GSM_BTS_TYPE_UNKNOWN,	"unknown" },
+	{ GSM_BTS_TYPE_BS11,	"bs11" },
+	{ GSM_BTS_TYPE_NANOBTS,	"nanobts" },
+	{ GSM_BTS_TYPE_RBS2000,	"rbs2000" },
+	{ GSM_BTS_TYPE_HSL_FEMTO, "hsl_femto" },
+	{ 0,			NULL }
+};
+
+enum gsm_bts_type parse_btstype(const char *arg)
+{
+	return get_string_value(bts_types, arg);
+}
+
+const char *btstype2str(enum gsm_bts_type type)
+{
+	return get_value_string(bts_types, type);
+}
+
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr)
+{
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		if (trx->nr == nr)
+			return trx;
+	}
+	return NULL;
+}
+
+/* Search for a BTS in the given Location Area; optionally start searching
+ * with start_bts (for continuing to search after the first result) */
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+				struct gsm_bts *start_bts)
+{
+	int i;
+	struct gsm_bts *bts;
+	int skip = 0;
+
+	if (start_bts)
+		skip = 1;
+
+	for (i = 0; i < net->num_bts; i++) {
+		bts = gsm_bts_num(net, i);
+
+		if (skip) {
+			if (start_bts == bts)
+				skip = 0;
+			continue;
+		}
+
+		if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac)
+			return bts;
+	}
+	return NULL;
+}
+
+static const struct value_string auth_policy_names[] = {
+	{ GSM_AUTH_POLICY_CLOSED,	"closed" },
+	{ GSM_AUTH_POLICY_ACCEPT_ALL,	"accept-all" },
+	{ GSM_AUTH_POLICY_TOKEN,	"token" },
+	{ 0,				NULL }
+};
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg)
+{
+	return get_string_value(auth_policy_names, arg);
+}
+
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy)
+{
+	return get_value_string(auth_policy_names, policy);
+}
+
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts)
+{
+	raid->mcc = bts->network->country_code;
+	raid->mnc = bts->network->network_code;
+	raid->lac = bts->location_area_code;
+	raid->rac = bts->gprs.rac;
+}
+
+int gsm48_ra_id_by_bts(u_int8_t *buf, struct gsm_bts *bts)
+{
+	struct gprs_ra_id raid;
+
+	gprs_ra_id_by_bts(&raid, bts);
+
+	return gsm48_construct_ra(buf, &raid);
+}
+
+static const struct value_string rrlp_mode_names[] = {
+	{ RRLP_MODE_NONE,	"none" },
+	{ RRLP_MODE_MS_BASED,	"ms-based" },
+	{ RRLP_MODE_MS_PREF,	"ms-preferred" },
+	{ RRLP_MODE_ASS_PREF,	"ass-preferred" },
+	{ 0,			NULL }
+};
+
+enum rrlp_mode rrlp_mode_parse(const char *arg)
+{
+	return get_string_value(rrlp_mode_names, arg);
+}
+
+const char *rrlp_mode_name(enum rrlp_mode mode)
+{
+	return get_value_string(rrlp_mode_names, mode);
+}
+
+static const struct value_string bts_gprs_mode_names[] = {
+	{ BTS_GPRS_NONE,	"none" },
+	{ BTS_GPRS_GPRS,	"gprs" },
+	{ BTS_GPRS_EGPRS,	"egprs" },
+	{ 0,			NULL }
+};
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg)
+{
+	return get_string_value(bts_gprs_mode_names, arg);
+}
+
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode)
+{
+	return get_value_string(bts_gprs_mode_names, mode);
+}
+
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan)
+{
+	struct gsm_meas_rep *meas_rep;
+
+	meas_rep = &lchan->meas_rep[lchan->meas_rep_idx];
+	memset(meas_rep, 0, sizeof(*meas_rep));
+	meas_rep->lchan = lchan;
+	lchan->meas_rep_idx = (lchan->meas_rep_idx + 1)
+					% ARRAY_SIZE(lchan->meas_rep);
+
+	return meas_rep;
+}
+
+int gsm_btsmodel_set_feature(struct gsm_bts_model *bts, enum gsm_bts_features feat)
+{
+	return bitvec_set_bit_pos(&bts->features, feat, 1);
+}
+
+int gsm_bts_has_feature(struct gsm_bts *bts, enum gsm_bts_features feat)
+{
+	return bitvec_get_bit_pos(&bts->model->features, feat);
+}
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
+{
+	struct gsm_bts_model *model;
+
+	model = bts_model_find(type);
+	if (!model)
+		return -EINVAL;
+
+	bts->type = type;
+	bts->model = model;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_HSL_FEMTO:
+		bts->c0->rsl_tei = 0;
+	case GSM_BTS_TYPE_NANOBTS:
+		/* Set the default OML Stream ID to 0xff */
+		bts->oml_tei = 0xff;
+		bts->c0->nominal_power = 23;
+		break;
+	case GSM_BTS_TYPE_BS11:
+	case GSM_BTS_TYPE_UNKNOWN:
+		break;
+	case GSM_BTS_TYPE_RBS2000:
+		INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups);
+		INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups);
+		break;
+	}
+
+	return 0;
+}
diff --git a/src/libcommon/socket.c b/src/libcommon/socket.c
new file mode 100644
index 0000000..47778e7
--- /dev/null
+++ b/src/libcommon/socket.c
@@ -0,0 +1,108 @@
+/* OpenBSC sokcet code, taken from Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/tlv.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/talloc.h>
+
+int make_sock(struct bsc_fd *bfd, int proto, u_int32_t ip, u_int16_t port,
+	      int (*cb)(struct bsc_fd *fd, unsigned int what))
+{
+	struct sockaddr_in addr;
+	int ret, on = 1;
+	int type = SOCK_STREAM;
+
+	switch (proto) {
+	case IPPROTO_TCP:
+		type = SOCK_STREAM;
+		break;
+	case IPPROTO_UDP:
+		type = SOCK_DGRAM;
+		break;
+	case IPPROTO_GRE:
+		type = SOCK_RAW;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	bfd->fd = socket(AF_INET, type, proto);
+	bfd->cb = cb;
+	bfd->when = BSC_FD_READ;
+	//bfd->data = line;
+
+	if (bfd->fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not create socket.\n");
+		return -EIO;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	if (ip)
+		addr.sin_addr.s_addr = htonl(ip);
+	else
+		addr.sin_addr.s_addr = INADDR_ANY;
+
+	setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+	if (ret < 0) {
+		LOGP(DINP, LOGL_ERROR, "could not bind socket %s\n",
+			strerror(errno));
+		close(bfd->fd);
+		return -EIO;
+	}
+
+	if (proto == IPPROTO_TCP) {
+		ret = listen(bfd->fd, 1);
+		if (ret < 0) {
+			perror("listen");
+			close(bfd->fd);
+			return ret;
+		}
+	}
+
+	ret = bsc_register_fd(bfd);
+	if (ret < 0) {
+		perror("register_listen_fd");
+		close(bfd->fd);
+		return ret;
+	}
+	return 0;
+}
diff --git a/src/libcommon/talloc_ctx.c b/src/libcommon/talloc_ctx.c
new file mode 100644
index 0000000..8e7ec23
--- /dev/null
+++ b/src/libcommon/talloc_ctx.c
@@ -0,0 +1,38 @@
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+
+extern void *tall_msgb_ctx;
+extern void *tall_fle_ctx;
+extern void *tall_locop_ctx;
+extern void *tall_authciphop_ctx;
+extern void *tall_gsms_ctx;
+extern void *tall_subscr_ctx;
+extern void *tall_sub_req_ctx;
+extern void *tall_call_ctx;
+extern void *tall_paging_ctx;
+extern void *tall_sigh_ctx;
+extern void *tall_tqe_ctx;
+extern void *tall_trans_ctx;
+extern void *tall_map_ctx;
+extern void *tall_upq_ctx;
+extern void *tall_ctr_ctx;
+
+void talloc_ctx_init(void)
+{
+	tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+	tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0,
+					  "bs11_file_list_entry");
+	tall_locop_ctx = talloc_named_const(tall_bsc_ctx, 0, "loc_updating_oper");
+	tall_authciphop_ctx = talloc_named_const(tall_bsc_ctx, 0, "auth_ciph_oper");
+	tall_gsms_ctx = talloc_named_const(tall_bsc_ctx, 0, "sms");
+	tall_subscr_ctx = talloc_named_const(tall_bsc_ctx, 0, "subscriber");
+	tall_sub_req_ctx = talloc_named_const(tall_bsc_ctx, 0, "subscr_request");
+	tall_call_ctx = talloc_named_const(tall_bsc_ctx, 0, "gsm_call");
+	tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request");
+	tall_sigh_ctx = talloc_named_const(tall_bsc_ctx, 0, "signal_handler");
+	tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry");
+	tall_trans_ctx = talloc_named_const(tall_bsc_ctx, 0, "transaction");
+	tall_map_ctx = talloc_named_const(tall_bsc_ctx, 0, "trau_map_entry");
+	tall_upq_ctx = talloc_named_const(tall_bsc_ctx, 0, "trau_upq_entry");
+	tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
+}
diff --git a/src/libgb/Makefile.am b/src/libgb/Makefile.am
new file mode 100644
index 0000000..b48b177
--- /dev/null
+++ b/src/libgb/Makefile.am
@@ -0,0 +1,9 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libgb.a
+
+libgb_a_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c \
+		  gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c
+#gprs_llc.c gprs_llc_vty.c crc24.c
diff --git a/src/libgb/Makefile.in b/src/libgb/Makefile.in
new file mode 100644
index 0000000..7b9dbd9
--- /dev/null
+++ b/src/libgb/Makefile.in
@@ -0,0 +1,460 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libgb
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libgb_a_AR = $(AR) $(ARFLAGS)
+libgb_a_LIBADD =
+am_libgb_a_OBJECTS = gprs_ns.$(OBJEXT) gprs_ns_frgre.$(OBJEXT) \
+	gprs_ns_vty.$(OBJEXT) gprs_bssgp.$(OBJEXT) \
+	gprs_bssgp_util.$(OBJEXT) gprs_bssgp_vty.$(OBJEXT)
+libgb_a_OBJECTS = $(am_libgb_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libgb_a_SOURCES)
+DIST_SOURCES = $(libgb_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libgb.a
+libgb_a_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c \
+		  gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libgb/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libgb/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libgb.a: $(libgb_a_OBJECTS) $(libgb_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libgb.a
+	$(AM_V_AR)$(libgb_a_AR) libgb.a $(libgb_a_OBJECTS) $(libgb_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libgb.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_bssgp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_bssgp_util.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_bssgp_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_ns.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_ns_frgre.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprs_ns_vty.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+#gprs_llc.c gprs_llc_vty.c crc24.c
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libgb/gprs_bssgp.c b/src/libgb/gprs_bssgp.c
new file mode 100644
index 0000000..eca34b9
--- /dev/null
+++ b/src/libgb/gprs_bssgp.c
@@ -0,0 +1,856 @@
+/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * TODO:
+ *  o  properly count incoming BVC-RESET packets in counter group
+ *  o  set log context as early as possible for outgoing packets
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/rate_ctr.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_llc.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_gmm.h>
+
+void *bssgp_tall_ctx = NULL;
+
+#define BVC_F_BLOCKED	0x0001
+
+enum bssgp_ctr {
+	BSSGP_CTR_PKTS_IN,
+	BSSGP_CTR_PKTS_OUT,
+	BSSGP_CTR_BYTES_IN,
+	BSSGP_CTR_BYTES_OUT,
+	BSSGP_CTR_BLOCKED,
+	BSSGP_CTR_DISCARDED,
+};
+
+static const struct rate_ctr_desc bssgp_ctr_description[] = {
+	{ "packets.in",	"Packets at BSSGP Level ( In)" },
+	{ "packets.out","Packets at BSSGP Level (Out)" },
+	{ "bytes.in",	"Bytes at BSSGP Level   ( In)" },
+	{ "bytes.out",	"Bytes at BSSGP Level   (Out)" },
+	{ "blocked",	"BVC Blocking count" },
+	{ "discarded",	"BVC LLC Discarded count" },
+};
+
+static const struct rate_ctr_group_desc bssgp_ctrg_desc = {
+	.group_name_prefix = "bssgp.bss_ctx",
+	.group_description = "BSSGP Peer Statistics",
+	.num_ctr = ARRAY_SIZE(bssgp_ctr_description),
+	.ctr_desc = bssgp_ctr_description,
+};
+
+LLIST_HEAD(bssgp_bvc_ctxts);
+
+/* Find a BTS Context based on parsed RA ID and Cell ID */
+struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid)
+{
+	struct bssgp_bvc_ctx *bctx;
+
+	llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
+		if (!memcmp(&bctx->ra_id, raid, sizeof(bctx->ra_id)) &&
+		    bctx->cell_id == cid)
+			return bctx;
+	}
+	return NULL;
+}
+
+/* Find a BTS context based on BVCI+NSEI tuple */
+struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei)
+{
+	struct bssgp_bvc_ctx *bctx;
+
+	llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
+		if (bctx->nsei == nsei && bctx->bvci == bvci)
+			return bctx;
+	}
+	return NULL;
+}
+
+struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
+{
+	struct bssgp_bvc_ctx *ctx;
+
+	ctx = talloc_zero(bssgp_tall_ctx, struct bssgp_bvc_ctx);
+	if (!ctx)
+		return NULL;
+	ctx->bvci = bvci;
+	ctx->nsei = nsei;
+	/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
+	ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
+
+	llist_add(&ctx->list, &bssgp_bvc_ctxts);
+
+	return ctx;
+}
+
+/* Chapter 10.4.5: Flow Control BVC ACK */
+static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* 10.3.7 SUSPEND-ACK PDU */
+int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli,
+			 const struct gprs_ra_id *ra_id, uint8_t suspend_ref)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint32_t _tlli;
+	uint8_t ra[6];
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = 0; /* Signalling */
+	bgph->pdu_type = BSSGP_PDUT_SUSPEND_ACK;
+
+	_tlli = htonl(tlli);
+	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli);
+	gsm48_construct_ra(ra, ra_id);
+	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra);
+	msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* 10.3.8 SUSPEND-NACK PDU */
+int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli,
+			  const struct gprs_ra_id *ra_id,
+			  uint8_t *cause)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint32_t _tlli;
+	uint8_t ra[6];
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = 0; /* Signalling */
+	bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK;
+
+	_tlli = htonl(tlli);
+	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli);
+	gsm48_construct_ra(ra, ra_id);
+	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra);
+	if (cause)
+		msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* 10.3.10 RESUME-ACK PDU */
+int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli,
+			const struct gprs_ra_id *ra_id)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint32_t _tlli;
+	uint8_t ra[6];
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = 0; /* Signalling */
+	bgph->pdu_type = BSSGP_PDUT_RESUME_ACK;
+
+	_tlli = htonl(tlli);
+	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli);
+	gsm48_construct_ra(ra, ra_id);
+	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* 10.3.11 RESUME-NACK PDU */
+int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli,
+			 const struct gprs_ra_id *ra_id, uint8_t *cause)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint32_t _tlli;
+	uint8_t ra[6];
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = 0; /* Signalling */
+	bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK;
+
+	_tlli = htonl(tlli);
+	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli);
+	gsm48_construct_ra(ra, ra_id);
+	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra);
+	if (cause)
+		msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
+{
+	/* 6 octets RAC */
+	gsm48_parse_ra(raid, buf);
+	/* 2 octets CID */
+	return ntohs(*(uint16_t *) (buf+6));
+}
+
+/* Chapter 8.4 BVC-Reset Procedure */
+static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,	
+			      uint16_t ns_bvci)
+{
+	struct bssgp_bvc_ctx *bctx;
+	uint16_t nsei = msgb_nsei(msg);
+	uint16_t bvci;
+	int rc;
+
+	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+	DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
+		bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
+
+	/* look-up or create the BTS context for this BVC */
+	bctx = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bctx)
+		bctx = btsctx_alloc(bvci, nsei);
+
+	/* As opposed to NS-VCs, BVCs are NOT blocked after RESET */
+	bctx->state &= ~BVC_S_BLOCKED;
+
+	/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
+	 * informs us about its RAC + Cell ID, so we can create a mapping */
+	if (bvci != 0 && bvci != 1) {
+		if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) {
+			LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u RESET "
+				"missing mandatory IE\n", bvci);
+			return -EINVAL;
+		}
+		/* actually extract RAC / CID */
+		bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
+						TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+		LOGP(DBSSGP, LOGL_NOTICE, "Cell %u-%u-%u-%u CI %u on BVCI %u\n",
+			bctx->ra_id.mcc, bctx->ra_id.mnc, bctx->ra_id.lac,
+			bctx->ra_id.rac, bctx->cell_id, bvci);
+	}
+
+	/* Acknowledge the RESET to the BTS */
+	rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+				  nsei, bvci, ns_bvci);
+	return 0;
+}
+
+static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp)
+{
+	uint16_t bvci;
+	struct bssgp_bvc_ctx *ptp_ctx;
+
+	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+	if (bvci == BVCI_SIGNALLING) {
+		/* 8.3.2: Signalling BVC shall never be blocked */
+		LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+			"received block for signalling BVC!?!\n",
+			msgb_nsei(msg), msgb_bvci(msg));
+		return 0;
+	}
+
+	LOGP(DBSSGP, LOGL_INFO, "BSSGP BVCI=%u BVC-BLOCK\n", bvci);
+
+	ptp_ctx = btsctx_by_bvci_nsei(bvci, msgb_nsei(msg));
+	if (!ptp_ctx)
+		return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
+
+	ptp_ctx->state |= BVC_S_BLOCKED;
+	rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]);
+
+	/* FIXME: Send NM_BVC_BLOCK.ind to NM */
+
+	/* We always acknowledge the BLOCKing */
+	return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK, msgb_nsei(msg),
+				    bvci, msgb_bvci(msg));
+};
+
+static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp)
+{
+	uint16_t bvci;
+	struct bssgp_bvc_ctx *ptp_ctx;
+
+	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI));
+	if (bvci == BVCI_SIGNALLING) {
+		/* 8.3.2: Signalling BVC shall never be blocked */
+		LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+			"received unblock for signalling BVC!?!\n",
+			msgb_nsei(msg), msgb_bvci(msg));
+		return 0;
+	}
+
+	DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
+
+	ptp_ctx = btsctx_by_bvci_nsei(bvci, msgb_nsei(msg));
+	if (!ptp_ctx)
+		return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
+
+	ptp_ctx->state &= ~BVC_S_BLOCKED;
+
+	/* FIXME: Send NM_BVC_UNBLOCK.ind to NM */
+
+	/* We always acknowledge the unBLOCKing */
+	return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, msgb_nsei(msg),
+				    bvci, msgb_bvci(msg));
+};
+
+/* Uplink unit-data */
+static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp,
+			  struct bssgp_bvc_ctx *ctx)
+{
+	struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+
+	/* extract TLLI and parse TLV IEs */
+	msgb_tlli(msg) = ntohl(budh->tlli);
+
+	DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x UPLINK-UNITDATA\n", msgb_tlli(msg));
+
+	/* Cell ID and LLC_PDU are the only mandatory IE */
+	if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
+		LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
+			"missing mandatory IE\n", msgb_tlli(msg));
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+	}
+
+	/* store pointer to LLC header and CELL ID in msgb->cb */
+	msgb_llch(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_LLC_PDU);
+	msgb_bcid(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_CELL_ID);
+
+	return gprs_llc_rcvmsg(msg, tp);
+}
+
+static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp,
+			    struct bssgp_bvc_ctx *ctx)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct gprs_ra_id raid;
+	uint32_t tlli;
+	int rc;
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
+		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
+			"missing mandatory IE\n", ctx->bvci);
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+	}
+
+	tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI));
+
+	DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
+		ctx->bvci, tlli);
+
+	gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
+
+	/* Inform GMM about the SUSPEND request */
+	rc = gprs_gmm_rx_suspend(&raid, tlli);
+	if (rc < 0)
+		return bssgp_tx_suspend_nack(msgb_nsei(msg), tlli, &raid, NULL);
+
+	bssgp_tx_suspend_ack(msgb_nsei(msg), tlli, &raid, 0);
+
+	return 0;
+}
+
+static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp,
+			   struct bssgp_bvc_ctx *ctx)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct gprs_ra_id raid;
+	uint32_t tlli;
+	uint8_t suspend_ref;
+	int rc;
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) {
+		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
+			"missing mandatory IE\n", ctx->bvci);
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+	}
+
+	tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI));
+	suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR);
+
+	DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x RESUME\n", ctx->bvci, tlli);
+
+	gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
+
+	/* Inform GMM about the RESUME request */
+	rc = gprs_gmm_rx_resume(&raid, tlli, suspend_ref);
+	if (rc < 0)
+		return bssgp_tx_resume_nack(msgb_nsei(msg), tlli, &raid,
+					    NULL);
+
+	bssgp_tx_resume_ack(msgb_nsei(msg), tlli, &raid);
+	return 0;
+}
+
+
+static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp,
+			     struct bssgp_bvc_ctx *ctx)
+{
+	uint32_t tlli = 0;
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) {
+		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
+			"missing mandatory IE\n", ctx->bvci);
+	}
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_TLLI))
+		tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI));
+
+	DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%08x LLC DISCARDED\n",
+		ctx->bvci, tlli);
+
+	rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]);
+
+	/* FIXME: send NM_LLC_DISCARDED to NM */
+	return 0;
+}
+
+static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
+			   struct bssgp_bvc_ctx *bctx)
+{
+
+	DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
+		bctx->bvci);
+
+	if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) ||
+	    !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) {
+		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
+			"missing mandatory IE\n", bctx->bvci);
+		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+	}
+
+	/* FIXME: actually implement flow control */
+
+	/* Send FLOW_CONTROL_BVC_ACK */
+	return bssgp_tx_fc_bvc_ack(msgb_nsei(msg), *TLVP_VAL(tp, BSSGP_IE_TAG),
+				   msgb_bvci(msg));
+}
+
+/* Receive a BSSGP PDU from a BSS on a PTP BVCI */
+static int gprs_bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
+			     struct bssgp_bvc_ctx *bctx)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	uint8_t pdu_type = bgph->pdu_type;
+	int rc = 0;
+
+	/* If traffic is received on a BVC that is marked as blocked, the
+	 * received PDU shall not be accepted and a STATUS PDU (Cause value:
+	 * BVC Blocked) shall be sent to the peer entity on the signalling BVC */
+	if (bctx->state & BVC_S_BLOCKED && pdu_type != BSSGP_PDUT_STATUS) {
+		uint16_t bvci = msgb_bvci(msg);
+		return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &bvci, msg);
+	}
+
+	switch (pdu_type) {
+	case BSSGP_PDUT_UL_UNITDATA:
+		/* some LLC data from the MS */
+		rc = bssgp_rx_ul_ud(msg, tp, bctx);
+		break;
+	case BSSGP_PDUT_RA_CAPABILITY:
+		/* BSS requests RA capability or IMSI */
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
+			bctx->bvci);
+		/* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */
+		/* FIXME: send RA_CAPA_UPDATE_ACK */
+		break;
+	case BSSGP_PDUT_RADIO_STATUS:
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
+		/* BSS informs us of some exception */
+		/* FIXME: send GMM_RADIO_STATUS.ind to GMM */
+		break;
+	case BSSGP_PDUT_FLOW_CONTROL_BVC:
+		/* BSS informs us of available bandwidth in Gb interface */
+		rc = bssgp_rx_fc_bvc(msg, tp, bctx);
+		break;
+	case BSSGP_PDUT_FLOW_CONTROL_MS:
+		/* BSS informs us of available bandwidth to one MS */
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
+			bctx->bvci);
+		/* FIXME: actually implement flow control */
+		/* FIXME: Send FLOW_CONTROL_MS_ACK */
+		break;
+	case BSSGP_PDUT_STATUS:
+		/* Some exception has occurred */
+		/* FIXME: send NM_STATUS.ind to NM */
+	case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
+	case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
+	case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
+	case BSSGP_PDUT_MODIFY_BSS_PFC:
+	case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x not [yet] "
+			"implemented\n", bctx->bvci, pdu_type);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
+		break;
+	/* those only exist in the SGSN -> BSS direction */
+	case BSSGP_PDUT_DL_UNITDATA:
+	case BSSGP_PDUT_PAGING_PS:
+	case BSSGP_PDUT_PAGING_CS:
+	case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
+	case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
+	case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type 0x%02x only exists "
+			"in DL\n", bctx->bvci, pdu_type);
+		bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+		rc = -EINVAL;
+		break;
+	default:
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type 0x%02x unknown\n",
+			bctx->bvci, pdu_type);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+		break;
+	}
+
+	return rc;
+}
+
+/* Receive a BSSGP PDU from a BSS on a SIGNALLING BVCI */
+static int gprs_bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
+				struct bssgp_bvc_ctx *bctx)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	uint8_t pdu_type = bgph->pdu_type;
+	int rc = 0;
+	uint16_t ns_bvci = msgb_bvci(msg);
+	uint16_t bvci;
+
+	switch (bgph->pdu_type) {
+	case BSSGP_PDUT_SUSPEND:
+		/* MS wants to suspend */
+		rc = bssgp_rx_suspend(msg, tp, bctx);
+		break;
+	case BSSGP_PDUT_RESUME:
+		/* MS wants to resume */
+		rc = bssgp_rx_resume(msg, tp, bctx);
+		break;
+	case BSSGP_PDUT_FLUSH_LL_ACK:
+		/* BSS informs us it has performed LL FLUSH */
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx FLUSH LL ACK\n", bctx->bvci);
+		/* FIXME: send NM_FLUSH_LL.res to NM */
+		break;
+	case BSSGP_PDUT_LLC_DISCARD:
+		/* BSS informs that some LLC PDU's have been discarded */
+		rc = bssgp_rx_llc_disc(msg, tp, bctx);
+		break;
+	case BSSGP_PDUT_BVC_BLOCK:
+		/* BSS tells us that BVC shall be blocked */
+		if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
+		    !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
+			LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
+				"missing mandatory IE\n");
+			goto err_mand_ie;
+		}
+		rc = bssgp_rx_bvc_block(msg, tp);
+		break;
+	case BSSGP_PDUT_BVC_UNBLOCK:
+		/* BSS tells us that BVC shall be unblocked */
+		if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
+			LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
+				"missing mandatory IE\n");
+			goto err_mand_ie;
+		}
+		rc = bssgp_rx_bvc_unblock(msg, tp);
+		break;
+	case BSSGP_PDUT_BVC_RESET:
+		/* BSS tells us that BVC init is required */
+		if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
+		    !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
+			LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
+				"missing mandatory IE\n");
+			goto err_mand_ie;
+		}
+		rc = bssgp_rx_bvc_reset(msg, tp, ns_bvci);
+		break;
+	case BSSGP_PDUT_STATUS:
+		/* Some exception has occurred */
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC STATUS\n", bctx->bvci);
+		/* FIXME: send NM_STATUS.ind to NM */
+		break;
+	/* those only exist in the SGSN -> BSS direction */
+	case BSSGP_PDUT_PAGING_PS:
+	case BSSGP_PDUT_PAGING_CS:
+	case BSSGP_PDUT_SUSPEND_ACK:
+	case BSSGP_PDUT_SUSPEND_NACK:
+	case BSSGP_PDUT_RESUME_ACK:
+	case BSSGP_PDUT_RESUME_NACK:
+	case BSSGP_PDUT_FLUSH_LL:
+	case BSSGP_PDUT_BVC_BLOCK_ACK:
+	case BSSGP_PDUT_BVC_UNBLOCK_ACK:
+	case BSSGP_PDUT_SGSN_INVOKE_TRACE:
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x only exists "
+			"in DL\n", bctx->bvci, pdu_type);
+		bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+		rc = -EINVAL;
+		break;
+	default:
+		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x unknown\n",
+			bctx->bvci, pdu_type);
+		rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+		break;
+	}
+
+	return rc;
+err_mand_ie:
+	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+}
+
+/* We expect msgb_bssgph() to point to the BSSGP header */
+int gprs_bssgp_rcvmsg(struct msgb *msg)
+{
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_bssgph(msg);
+	struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+	struct tlv_parsed tp;
+	struct bssgp_bvc_ctx *bctx;
+	uint8_t pdu_type = bgph->pdu_type;
+	uint16_t ns_bvci = msgb_bvci(msg);
+	int data_len;
+	int rc = 0;
+
+	/* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */
+
+	/* UNITDATA BSSGP headers have TLLI in front */
+	if (pdu_type != BSSGP_PDUT_UL_UNITDATA &&
+	    pdu_type != BSSGP_PDUT_DL_UNITDATA) {
+		data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+		rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
+	} else {
+		data_len = msgb_bssgp_len(msg) - sizeof(*budh);
+		rc = bssgp_tlv_parse(&tp, budh->data, data_len);
+	}
+
+	/* look-up or create the BTS context for this BVC */
+	bctx = btsctx_by_bvci_nsei(ns_bvci, msgb_nsei(msg));
+	/* Only a RESET PDU can create a new BVC context */
+	if (!bctx && pdu_type != BSSGP_PDUT_BVC_RESET) {
+		LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU "
+			"type %u for unknown BVCI\n", msgb_nsei(msg), ns_bvci,
+			pdu_type);
+		return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg);
+	}
+
+	if (bctx) {
+		log_set_context(BSC_CTX_BVC, bctx);
+		rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]);
+		rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN],
+			     msgb_bssgp_len(msg));
+	}
+
+	if (ns_bvci == BVCI_SIGNALLING)
+		rc = gprs_bssgp_rx_sign(msg, &tp, bctx);
+	else if (ns_bvci == BVCI_PTM)
+		rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
+	else
+		rc = gprs_bssgp_rx_ptp(msg, &tp, bctx);
+
+	return rc;
+}
+
+/* Entry function from upper level (LLC), asking us to transmit a BSSGP PDU
+ * to a remote MS (identified by TLLI) at a BTS identified by its BVCI and NSEI */
+int gprs_bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx)
+{
+	struct bssgp_bvc_ctx *bctx;
+	struct bssgp_ud_hdr *budh;
+	uint8_t llc_pdu_tlv_hdr_len = 2;
+	uint8_t *llc_pdu_tlv, *qos_profile;
+	uint16_t pdu_lifetime = 1000; /* centi-seconds */
+	uint8_t qos_profile_default[3] = { 0x00, 0x00, 0x20 };
+	uint16_t msg_len = msg->len;
+	uint16_t bvci = msgb_bvci(msg);
+	uint16_t nsei = msgb_nsei(msg);
+	uint16_t drx_params;
+
+	/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
+	if (bvci <= BVCI_PTM ) {
+		LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
+			bvci);
+		return -EINVAL;
+	}
+
+	bctx = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bctx) {
+		/* FIXME: don't simply create missing context, but reject message */
+		bctx = btsctx_alloc(bvci, nsei);
+	}
+
+	if (msg->len > TVLV_MAX_ONEBYTE)
+		llc_pdu_tlv_hdr_len += 1;
+
+	/* prepend the tag and length of the LLC-PDU TLV */
+	llc_pdu_tlv = msgb_push(msg, llc_pdu_tlv_hdr_len);
+	llc_pdu_tlv[0] = BSSGP_IE_LLC_PDU;
+	if (llc_pdu_tlv_hdr_len > 2) {
+		llc_pdu_tlv[1] = msg_len >> 8;
+		llc_pdu_tlv[2] = msg_len & 0xff;
+	} else {
+		llc_pdu_tlv[1] = msg_len & 0x7f;
+		llc_pdu_tlv[1] |= 0x80;
+	}
+
+	/* FIXME: optional elements: Alignment, UTRAN CCO, LSA, PFI */
+
+	if (mmctx) {
+		/* Old TLLI to help BSS map from old->new */
+#if 0
+		if (mmctx->tlli_old)
+			msgb_tvlv_push(msg, BSSGP_IE_TLLI, 4, htonl(*tlli_old));
+#endif
+
+		/* IMSI */
+		if (strlen(mmctx->imsi)) {
+			uint8_t mi[10];
+			int imsi_len = gsm48_generate_mid_from_imsi(mi, mmctx->imsi);
+			if (imsi_len > 2)
+				msgb_tvlv_push(msg, BSSGP_IE_IMSI,
+							imsi_len-2, mi+2);
+		}
+
+		/* DRX parameters */
+		drx_params = htons(mmctx->drx_parms);
+		msgb_tvlv_push(msg, BSSGP_IE_DRX_PARAMS, 2,
+				(uint8_t *) &drx_params);
+
+		/* FIXME: Priority */
+
+		/* MS Radio Access Capability */
+		if (mmctx->ms_radio_access_capa.len)
+			msgb_tvlv_push(msg, BSSGP_IE_MS_RADIO_ACCESS_CAP,
+					mmctx->ms_radio_access_capa.len,
+					mmctx->ms_radio_access_capa.buf);
+	}
+
+	/* prepend the pdu lifetime */
+	pdu_lifetime = htons(pdu_lifetime);
+	msgb_tvlv_push(msg, BSSGP_IE_PDU_LIFETIME, 2, (uint8_t *)&pdu_lifetime);
+
+	/* prepend the QoS profile, TLLI and pdu type */
+	budh = (struct bssgp_ud_hdr *) msgb_push(msg, sizeof(*budh));
+	memcpy(budh->qos_profile, qos_profile_default, sizeof(qos_profile_default));
+	budh->tlli = htonl(msgb_tlli(msg));
+	budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
+
+	rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
+	rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+
+	/* Identifiers down: BVCI, NSEI (in msgb->cb) */
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* Send a single GMM-PAGING.req to a given NSEI/NS-BVCI */
+int gprs_bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
+			 struct bssgp_paging_info *pinfo)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint16_t drx_params = htons(pinfo->drx_params);
+	uint8_t mi[10];
+	int imsi_len = gsm48_generate_mid_from_imsi(mi, pinfo->imsi);
+	uint8_t ra[6];
+
+	if (imsi_len < 2)
+		return -EINVAL;
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	if (pinfo->mode == BSSGP_PAGING_PS)
+		bgph->pdu_type = BSSGP_PDUT_PAGING_PS;
+	else
+		bgph->pdu_type = BSSGP_PDUT_PAGING_CS;
+	/* IMSI */
+	msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
+	/* DRX Parameters */
+	msgb_tvlv_put(msg, BSSGP_IE_DRX_PARAMS, 2,
+			(uint8_t *) &drx_params);
+	/* Scope */
+	switch (pinfo->scope) {
+	case BSSGP_PAGING_BSS_AREA:
+		{
+			uint8_t null = 0;
+			msgb_tvlv_put(msg, BSSGP_IE_BSS_AREA_ID, 1, &null);
+		}
+		break;
+	case BSSGP_PAGING_LOCATION_AREA:
+		gsm48_construct_ra(ra, &pinfo->raid);
+		msgb_tvlv_put(msg, BSSGP_IE_LOCATION_AREA, 4, ra);
+		break;
+	case BSSGP_PAGING_ROUTEING_AREA:
+		gsm48_construct_ra(ra, &pinfo->raid);
+		msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra);
+		break;
+	case BSSGP_PAGING_BVCI:
+		{
+			uint16_t bvci = htons(pinfo->bvci);
+			msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *)&bvci);
+		}
+		break;
+	}
+	/* QoS profile mandatory for PS */
+	if (pinfo->mode == BSSGP_PAGING_PS)
+		msgb_tvlv_put(msg, BSSGP_IE_QOS_PROFILE, 3, pinfo->qos);
+
+	/* Optional (P-)TMSI */
+	if (pinfo->ptmsi) {
+		uint32_t ptmsi = htonl(*pinfo->ptmsi);
+		msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *) &ptmsi);
+	}
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
diff --git a/src/libgb/gprs_bssgp_util.c b/src/libgb/gprs_bssgp_util.c
new file mode 100644
index 0000000..f8e3b56
--- /dev/null
+++ b/src/libgb/gprs_bssgp_util.c
@@ -0,0 +1,119 @@
+/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_ns.h>
+
+struct gprs_ns_inst *bssgp_nsi;
+
+/* BSSGP Protocol specific, not implementation specific */
+/* FIXME: This needs to go into libosmocore after finished */
+
+/* Chapter 11.3.9 / Table 11.10: Cause coding */
+static const struct value_string bssgp_cause_strings[] = {
+	{ BSSGP_CAUSE_PROC_OVERLOAD,	"Processor overload" },
+	{ BSSGP_CAUSE_EQUIP_FAIL,	"Equipment Failure" },
+	{ BSSGP_CAUSE_TRASIT_NET_FAIL,	"Transit netowkr service failure" },
+	{ BSSGP_CAUSE_CAPA_GREATER_0KPBS,"Transmission capacity modified" },
+	{ BSSGP_CAUSE_UNKNOWN_MS,	"Unknown MS" },
+	{ BSSGP_CAUSE_UNKNOWN_BVCI,	"Unknown BVCI" },
+	{ BSSGP_CAUSE_CELL_TRAF_CONG,	"Cell traffic congestion" },
+	{ BSSGP_CAUSE_SGSN_CONG,	"SGSN congestion" },
+	{ BSSGP_CAUSE_OML_INTERV,	"O&M intervention" },
+	{ BSSGP_CAUSE_BVCI_BLOCKED,	"BVCI blocked" },
+	{ BSSGP_CAUSE_PFC_CREATE_FAIL,	"PFC create failure" },
+	{ BSSGP_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
+	{ BSSGP_CAUSE_INV_MAND_INF,	"Invalid mandatory information" },
+	{ BSSGP_CAUSE_MISSING_MAND_IE,	"Missing mandatory IE" },
+	{ BSSGP_CAUSE_MISSING_COND_IE,	"Missing conditional IE" },
+	{ BSSGP_CAUSE_UNEXP_COND_IE,	"Unexpected conditional IE" },
+	{ BSSGP_CAUSE_COND_IE_ERR,	"Conditional IE error" },
+	{ BSSGP_CAUSE_PDU_INCOMP_STATE,	"PDU incompatible with protocol state" },
+	{ BSSGP_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error - unspecified" },
+	{ BSSGP_CAUSE_PDU_INCOMP_FEAT, 	"PDU not compatible with feature set" },
+	{ 0, NULL },
+};
+
+const char *bssgp_cause_str(enum gprs_bssgp_cause cause)
+{
+	return get_value_string(bssgp_cause_strings, cause);
+}
+
+
+struct msgb *bssgp_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(4096, 128, "BSSGP");
+}
+
+/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
+int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
+			 uint16_t bvci, uint16_t ns_bvci)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	uint16_t _bvci;
+
+	msgb_nsei(msg) = nsei;
+	msgb_bvci(msg) = ns_bvci;
+
+	bgph->pdu_type = pdu_type;
+	_bvci = htons(bvci);
+	msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
+/* Chapter 10.4.14: Status */
+int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph =
+			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+
+	LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
+		bvci ? *bvci : 0, bssgp_cause_str(cause));
+	msgb_nsei(msg) = msgb_nsei(orig_msg);
+	msgb_bvci(msg) = 0;
+
+	bgph->pdu_type = BSSGP_PDUT_STATUS;
+	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
+	if (bvci) {
+		uint16_t _bvci = htons(*bvci);
+		msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+	}
+	if (orig_msg)
+		msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR,
+			      msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg));
+
+	return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
diff --git a/src/libgb/gprs_bssgp_vty.c b/src/libgb/gprs_bssgp_vty.c
new file mode 100644
index 0000000..9ebd090
--- /dev/null
+++ b/src/libgb/gprs_bssgp_vty.c
@@ -0,0 +1,176 @@
+/* VTY interface for our GPRS BSS Gateway Protocol (BSSGP) implementation */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+/* FIXME: this should go to some common file as it is copied
+ * in vty_interface.c of the BSC */
+static const struct value_string gprs_bssgp_timer_strs[] = {
+	{ 0, NULL }
+};
+
+static struct cmd_node bssgp_node = {
+	BSSGP_NODE,
+	"%s(bssgp)#",
+	1,
+};
+
+static int config_write_bssgp(struct vty *vty)
+{
+	vty_out(vty, "bssgp%s", VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bssgp, cfg_bssgp_cmd,
+      "bssgp",
+      "Configure the GPRS BSS Gateway Protocol")
+{
+	vty->node = BSSGP_NODE;
+	return CMD_SUCCESS;
+}
+
+static void dump_bvc(struct vty *vty, struct bssgp_bvc_ctx *bvc, int stats)
+{
+	vty_out(vty, "NSEI %5u, BVCI %5u, RA-ID: %u-%u-%u-%u, CID: %u, "
+		"STATE: %s%s", bvc->nsei, bvc->bvci, bvc->ra_id.mcc,
+		bvc->ra_id.mnc, bvc->ra_id.lac, bvc->ra_id.rac, bvc->cell_id,
+		bvc->state & BVC_S_BLOCKED ? "BLOCKED" : "UNBLOCKED",
+		VTY_NEWLINE);
+	if (stats)
+		vty_out_rate_ctr_group(vty, " ", bvc->ctrg);
+}
+
+static void dump_bssgp(struct vty *vty, int stats)
+{
+	struct bssgp_bvc_ctx *bvc;
+
+	llist_for_each_entry(bvc, &bssgp_bvc_ctxts, list) {
+		dump_bvc(vty, bvc, stats);
+	}
+}
+
+#define BSSGP_STR "Show information about the BSSGP protocol\n"
+
+DEFUN(show_bssgp, show_bssgp_cmd, "show bssgp",
+	SHOW_STR BSSGP_STR)
+{
+	dump_bssgp(vty, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bssgp_stats, show_bssgp_stats_cmd, "show bssgp stats",
+	SHOW_STR BSSGP_STR
+	"Include statistics\n")
+{
+	dump_bssgp(vty, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bvc, show_bvc_cmd, "show bssgp nsei <0-65535> [stats]",
+	SHOW_STR BSSGP_STR
+	"Show all BVCs on one NSE\n"
+	"The NSEI\n" "Include Statistics\n")
+{
+	struct bssgp_bvc_ctx *bvc;
+	uint16_t nsei = atoi(argv[1]);
+	int show_stats = 0;
+
+	if (argc >= 2)
+		show_stats = 1;
+
+	llist_for_each_entry(bvc, &bssgp_bvc_ctxts, list) {
+		if (bvc->nsei != nsei)
+			continue;
+		dump_bvc(vty, bvc, show_stats);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_bvc,
+      logging_fltr_bvc_cmd,
+      "logging filter bvc nsei <0-65535> bvci <0-65535>",
+	LOGGING_STR FILTER_STR
+	"Filter based on BSSGP Virtual Connection\n"
+	"NSEI of the BVC to be filtered\n"
+	"Network Service Entity Identifier (NSEI)\n"
+	"BVCI of the BVC to be filtered\n"
+	"BSSGP Virtual Connection Identifier (BVCI)\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+	struct bssgp_bvc_ctx *bvc;
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t bvci = atoi(argv[1]);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	bvc = btsctx_by_bvci_nsei(bvci, nsei);
+	if (!bvc) {
+		vty_out(vty, "No BVC by that identifier%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_bvc_filter(tgt, bvc);
+	return CMD_SUCCESS;
+}
+
+int gprs_bssgp_vty_init(void)
+{
+	install_element_ve(&show_bssgp_cmd);
+	install_element_ve(&show_bssgp_stats_cmd);
+	install_element_ve(&show_bvc_cmd);
+	install_element_ve(&logging_fltr_bvc_cmd);
+
+	install_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
+
+	install_element(CONFIG_NODE, &cfg_bssgp_cmd);
+	install_node(&bssgp_node, config_write_bssgp);
+	install_default(BSSGP_NODE);
+	install_element(BSSGP_NODE, &ournode_exit_cmd);
+	install_element(BSSGP_NODE, &ournode_end_cmd);
+	//install_element(BSSGP_NODE, &cfg_bssgp_timer_cmd);
+
+	return 0;
+}
diff --git a/src/libgb/gprs_ns.c b/src/libgb/gprs_ns.c
new file mode 100644
index 0000000..5a8e358
--- /dev/null
+++ b/src/libgb/gprs_ns.c
@@ -0,0 +1,992 @@
+/* GPRS Networks Service (NS) messages on the Gb interfacebvci = msgb_bvci(msg);
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Some introduction into NS:  NS is used typically on top of frame relay,
+ * but in the ip.access world it is encapsulated in UDP packets.  It serves
+ * as an intermediate shim betwen BSSGP and the underlying medium.  It doesn't
+ * do much, apart from providing congestion notification and status indication.
+ *
+ * Terms:
+ * 	NS		Network Service
+ *	NSVC		NS Virtual Connection
+ *	NSEI		NS Entity Identifier
+ *	NSVL		NS Virtual Link
+ *	NSVLI		NS Virtual Link Identifier
+ *	BVC		BSSGP Virtual Connection
+ *	BVCI		BSSGP Virtual Connection Identifier
+ *	NSVCG		NS Virtual Connection Goup
+ *	Blocked		NS-VC cannot be used for user traffic
+ *	Alive		Ability of a NS-VC to provide communication
+ *
+ *  There can be multiple BSSGP virtual connections over one (group of) NSVC's.  BSSGP will
+ * therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
+ * NS then has to firgure out which NSVC's are responsible for this BVCI.
+ * Those mappings are administratively configured.
+ */
+
+/* This implementation has the following limitations:
+ *  o Only one NS-VC for each NSE: No load-sharing function
+ *  o NSVCI 65535 and 65534 are reserved for internal use
+ *  o Only UDP is supported as of now, no frame relay support
+ *  o The IP Sub-Network-Service (SNS) as specified in 48.016 is not implemented
+ *  o There are no BLOCK and UNBLOCK timers (yet?)
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/gprs_ns_frgre.h>
+#include <openbsc/socket.h>
+
+static const struct tlv_definition ns_att_tlvdef = {
+	.def = {
+		[NS_IE_CAUSE]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_VCI]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_PDU]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_BVCI]	= { TLV_TYPE_TvLV, 0 },
+		[NS_IE_NSEI]	= { TLV_TYPE_TvLV, 0 },
+	},
+};
+
+enum ns_ctr {
+	NS_CTR_PKTS_IN,
+	NS_CTR_PKTS_OUT,
+	NS_CTR_BYTES_IN,
+	NS_CTR_BYTES_OUT,
+	NS_CTR_BLOCKED,
+	NS_CTR_DEAD,
+};
+
+static const struct rate_ctr_desc nsvc_ctr_description[] = {
+	{ "packets.in", "Packets at NS Level ( In)" },
+	{ "packets.out","Packets at NS Level (Out)" },
+	{ "bytes.in",	"Bytes at NS Level   ( In)" },
+	{ "bytes.out",	"Bytes at NS Level   (Out)" },
+	{ "blocked",	"NS-VC Block count        " },
+	{ "dead",	"NS-VC gone dead count    " },
+};
+
+static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
+	.group_name_prefix = "ns.nsvc",
+	.group_description = "NSVC Peer Statistics",
+	.num_ctr = ARRAY_SIZE(nsvc_ctr_description),
+	.ctr_desc = nsvc_ctr_description,
+};
+
+/* Lookup struct gprs_nsvc based on NSVCI */
+struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi, uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->nsvci == nsvci)
+			return nsvc;
+	}
+	return NULL;
+}
+
+/* Lookup struct gprs_nsvc based on NSVCI */
+struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->nsei == nsei)
+			return nsvc;
+	}
+	return NULL;
+}
+
+/* Lookup struct gprs_nsvc based on remote peer socket addr */
+static struct gprs_nsvc *nsvc_by_rem_addr(struct gprs_ns_inst *nsi,
+					  struct sockaddr_in *sin)
+{
+	struct gprs_nsvc *nsvc;
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc->ip.bts_addr.sin_addr.s_addr ==
+					sin->sin_addr.s_addr &&
+		    nsvc->ip.bts_addr.sin_port == sin->sin_port)
+			return nsvc;
+	}
+	return NULL;
+}
+
+static void gprs_ns_timer_cb(void *data);
+
+struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	LOGP(DNS, LOGL_INFO, "NSVCI=%u Creating NS-VC\n", nsvci);
+
+	nsvc = talloc_zero(nsi, struct gprs_nsvc);
+	nsvc->nsvci = nsvci;
+	/* before RESET procedure: BLOCKED and DEAD */
+	nsvc->state = NSE_S_BLOCKED;
+	nsvc->nsi = nsi;
+	nsvc->timer.cb = gprs_ns_timer_cb;
+	nsvc->timer.data = nsvc;
+	nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, nsvci);
+
+	llist_add(&nsvc->list, &nsi->gprs_nsvcs);
+
+	return nsvc;
+}
+
+void nsvc_delete(struct gprs_nsvc *nsvc)
+{
+	if (bsc_timer_pending(&nsvc->timer))
+		bsc_del_timer(&nsvc->timer);
+	llist_del(&nsvc->list);
+	talloc_free(nsvc);
+}
+
+static void ns_dispatch_signal(struct gprs_nsvc *nsvc, unsigned int signal,
+			       uint8_t cause)
+{
+	struct ns_signal_data nssd;
+
+	nssd.nsvc = nsvc;
+	nssd.cause = cause;
+
+	dispatch_signal(SS_NS, signal, &nssd);
+}
+
+/* Section 10.3.2, Table 13 */
+static const struct value_string ns_cause_str[] = {
+	{ NS_CAUSE_TRANSIT_FAIL,	"Transit network failure" },
+	{ NS_CAUSE_OM_INTERVENTION, 	"O&M intervention" },
+	{ NS_CAUSE_EQUIP_FAIL,		"Equipment failure" },
+	{ NS_CAUSE_NSVC_BLOCKED,	"NS-VC blocked" },
+	{ NS_CAUSE_NSVC_UNKNOWN,	"NS-VC unknown" },
+	{ NS_CAUSE_BVCI_UNKNOWN,	"BVCI unknown" },
+	{ NS_CAUSE_SEM_INCORR_PDU,	"Semantically incorrect PDU" },
+	{ NS_CAUSE_PDU_INCOMP_PSTATE,	"PDU not compatible with protocol state" },
+	{ NS_CAUSE_PROTO_ERR_UNSPEC,	"Protocol error, unspecified" },
+	{ NS_CAUSE_INVAL_ESSENT_IE,	"Invalid essential IE" },
+	{ NS_CAUSE_MISSING_ESSENT_IE,	"Missing essential IE" },
+	{ 0, NULL }
+};
+
+const char *gprs_ns_cause_str(enum ns_cause cause)
+{
+	return get_value_string(ns_cause_str, cause);
+}
+
+static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
+extern int grps_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
+
+static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int ret;
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	/* Increment number of Uplink bytes */
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]);
+	rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg));
+
+	switch (nsvc->ll) {
+	case GPRS_NS_LL_UDP:
+		ret = nsip_sendmsg(nsvc, msg);
+		break;
+	case GPRS_NS_LL_FR_GRE:
+		ret = gprs_ns_frgre_sendmsg(nsvc, msg);
+		break;
+	default:
+		LOGP(DNS, LOGL_ERROR, "unsupported NS linklayer %u\n", nsvc->ll);
+		msgb_free(msg);
+		ret = -EIO;
+		break;
+	}
+	return ret;
+}
+
+static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
+{
+	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct gprs_ns_hdr *nsh;
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	if (!msg)
+		return -ENOMEM;
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+	nsh->pdu_type = pdu_type;
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+	uint16_t nsei = htons(nsvc->nsei);
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	if (!msg)
+		return -ENOMEM;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n",
+		nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause));
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	nsh->pdu_type = NS_PDUT_RESET;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+
+}
+
+int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause,
+		      uint16_t bvci, struct msgb *orig_msg)
+{
+	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	bvci = htons(bvci);
+
+	if (!msg)
+		return -ENOMEM;
+
+	LOGP(DNS, LOGL_NOTICE, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n",
+		nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause));
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	nsh->pdu_type = NS_PDUT_STATUS;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+
+	/* Section 9.2.7.1: Static conditions for NS-VCI */
+	if (cause == NS_CAUSE_NSVC_BLOCKED ||
+	    cause == NS_CAUSE_NSVC_UNKNOWN)
+		msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+
+	/* Section 9.2.7.2: Static conditions for NS PDU */
+	switch (cause) {
+	case NS_CAUSE_SEM_INCORR_PDU:
+	case NS_CAUSE_PDU_INCOMP_PSTATE:
+	case NS_CAUSE_PROTO_ERR_UNSPEC:
+	case NS_CAUSE_INVAL_ESSENT_IE:
+	case NS_CAUSE_MISSING_ESSENT_IE:
+		msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg),
+			      orig_msg->l2h);
+		break;
+	default:
+		break;
+	}
+
+	/* Section 9.2.7.3: Static conditions for BVCI */
+	if (cause == NS_CAUSE_BVCI_UNKNOWN)
+		msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci = htons(nsvc->nsvci);
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	if (!msg)
+		return -ENOMEM;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK (NSVCI=%u, cause=%s)\n",
+		nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause));
+
+	/* be conservative and mark it as blocked even now! */
+	nsvc->state |= NSE_S_BLOCKED;
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	nsh->pdu_type = NS_PDUT_BLOCK;
+
+	msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc)
+{
+	log_set_context(BSC_CTX_NSVC, nsvc);
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK);
+}
+
+int gprs_ns_tx_alive(struct gprs_nsvc *nsvc)
+{
+	log_set_context(BSC_CTX_NSVC, nsvc);
+	LOGP(DNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+}
+
+int gprs_ns_tx_alive_ack(struct gprs_nsvc *nsvc)
+{
+	log_set_context(BSC_CTX_NSVC, nsvc);
+	LOGP(DNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE_ACK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
+}
+
+static const enum ns_timeout timer_mode_tout[_NSVC_TIMER_NR] = {
+	[NSVC_TIMER_TNS_RESET]	= NS_TOUT_TNS_RESET,
+	[NSVC_TIMER_TNS_ALIVE]	= NS_TOUT_TNS_ALIVE,
+	[NSVC_TIMER_TNS_TEST]	= NS_TOUT_TNS_TEST,
+};
+
+static const struct value_string timer_mode_strs[] = {
+	{ NSVC_TIMER_TNS_RESET, "tns-reset" },
+	{ NSVC_TIMER_TNS_ALIVE, "tns-alive" },
+	{ NSVC_TIMER_TNS_TEST, "tns-test" },
+	{ 0, NULL }
+};
+
+static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
+{
+	enum ns_timeout tout = timer_mode_tout[mode];
+	unsigned int seconds = nsvc->nsi->timeout[tout];
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+	DEBUGP(DNS, "NSEI=%u Starting timer in mode %s (%u seconds)\n",
+		nsvc->nsei, get_value_string(timer_mode_strs, mode),
+		seconds);
+		
+	if (bsc_timer_pending(&nsvc->timer))
+		bsc_del_timer(&nsvc->timer);
+
+	nsvc->timer_mode = mode;
+	bsc_schedule_timer(&nsvc->timer, seconds, 0);
+}
+
+static void gprs_ns_timer_cb(void *data)
+{
+	struct gprs_nsvc *nsvc = data;
+	enum ns_timeout tout = timer_mode_tout[nsvc->timer_mode];
+	unsigned int seconds = nsvc->nsi->timeout[tout];
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+	DEBUGP(DNS, "NSEI=%u Timer expired in mode %s (%u seconds)\n",
+		nsvc->nsei, get_value_string(timer_mode_strs, nsvc->timer_mode),
+		seconds);
+		
+	switch (nsvc->timer_mode) {
+	case NSVC_TIMER_TNS_ALIVE:
+		/* Tns-alive case: we expired without response ! */
+		nsvc->alive_retries++;
+		if (nsvc->alive_retries >
+			nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+			/* mark as dead and blocked */
+			nsvc->state = NSE_S_BLOCKED;
+			rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+			rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+			LOGP(DNS, LOGL_NOTICE,
+				"NSEI=%u Tns-alive expired more then "
+				"%u times, blocking NS-VC\n", nsvc->nsei,
+				nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]);
+			ns_dispatch_signal(nsvc, S_NS_ALIVE_EXP, 0);
+			ns_dispatch_signal(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
+			return;
+		}
+		/* Tns-test case: send NS-ALIVE PDU */
+		gprs_ns_tx_alive(nsvc);
+		/* start Tns-alive timer */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_TEST:
+		/* Tns-test case: send NS-ALIVE PDU */
+		gprs_ns_tx_alive(nsvc);
+		/* start Tns-alive timer (transition into faster
+		 * alive retransmissions) */
+		nsvc->alive_retries = 0;
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
+		break;
+	case NSVC_TIMER_TNS_RESET:
+		/* Chapter 7.3: Re-send the RESET */
+		gprs_ns_tx_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+		/* Re-start Tns-reset timer */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
+		break;
+	case _NSVC_TIMER_NR:
+		break;
+	}
+}
+
+/* Section 9.2.6 */
+static int gprs_ns_tx_reset_ack(struct gprs_nsvc *nsvc)
+{
+	struct msgb *msg = gprs_ns_msgb_alloc();
+	struct gprs_ns_hdr *nsh;
+	uint16_t nsvci, nsei;
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+	if (!msg)
+		return -ENOMEM;
+
+	nsvci = htons(nsvc->nsvci);
+	nsei = htons(nsvc->nsei);
+
+	msg->l2h = msgb_put(msg, sizeof(*nsh));
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+	nsh->pdu_type = NS_PDUT_RESET_ACK;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n",
+		nsvc->nsei, nsvc->nsvci);
+
+	msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
+	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+/* Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive */
+int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
+{
+	struct gprs_nsvc *nsvc;
+	struct gprs_ns_hdr *nsh;
+	uint16_t bvci = msgb_bvci(msg);
+
+	nsvc = nsvc_by_nsei(nsi, msgb_nsei(msg));
+	if (!nsvc) {
+		LOGP(DNS, LOGL_ERROR, "Unable to resolve NSEI %u "
+			"to NS-VC!\n", msgb_nsei(msg));
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	if (!(nsvc->state & NSE_S_ALIVE)) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u is not alive, cannot send\n",
+			nsvc->nsei);
+		msgb_free(msg);
+		return -EBUSY;
+	}
+	if (nsvc->state & NSE_S_BLOCKED) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u is blocked, cannot send\n",
+			nsvc->nsei);
+		msgb_free(msg);
+		return -EBUSY;
+	}
+
+	msg->l2h = msgb_push(msg, sizeof(*nsh) + 3);
+	nsh = (struct gprs_ns_hdr *) msg->l2h;
+	if (!nsh) {
+		LOGP(DNS, LOGL_ERROR, "Not enough headroom for NS header\n");
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	nsh->pdu_type = NS_PDUT_UNITDATA;
+	/* spare octet in data[0] */
+	nsh->data[1] = bvci >> 8;
+	nsh->data[2] = bvci & 0xff;
+
+	return gprs_ns_tx(nsvc, msg);
+}
+
+/* Section 9.2.10: receive side */
+static int gprs_ns_rx_unitdata(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
+	uint16_t bvci;
+
+	if (nsvc->state & NSE_S_BLOCKED)
+		return gprs_ns_tx_status(nsvc, NS_CAUSE_NSVC_BLOCKED,
+					 0, msg);
+
+	/* spare octet in data[0] */
+	bvci = nsh->data[1] << 8 | nsh->data[2];
+	msgb_bssgph(msg) = &nsh->data[3];
+	msgb_bvci(msg) = bvci;
+
+	/* call upper layer (BSSGP) */
+	return nsvc->nsi->cb(GPRS_NS_EVT_UNIT_DATA, nsvc, msg, bvci);
+}
+
+/* Section 9.2.7 */
+static int gprs_ns_rx_status(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t cause;
+	int rc;
+
+	LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx NS STATUS ", nsvc->nsei);
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+			msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+	if (rc < 0) {
+		LOGPC(DNS, LOGL_NOTICE, "Error during TLV Parse\n");
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS STATUS: "
+			"Error during TLV Parse\n", nsvc->nsei);
+		return rc;
+	}
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE)) {
+		LOGPC(DNS, LOGL_INFO, "missing cause IE\n");
+		return -EINVAL;
+	}
+
+	cause = *TLVP_VAL(&tp, NS_IE_CAUSE);
+	LOGPC(DNS, LOGL_NOTICE, "cause=%s\n", gprs_ns_cause_str(cause));
+
+	return 0;
+}
+
+/* Section 7.3 */
+static int gprs_ns_rx_reset(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t *cause;
+	uint16_t *nsvci, *nsei;
+	int rc;
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+			msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+	if (rc < 0) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS RESET "
+			"Error during TLV Parse\n", nsvc->nsei);
+		return rc;
+	}
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+	    !TLVP_PRESENT(&tp, NS_IE_VCI) ||
+	    !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+		LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+		gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg);
+		return -EINVAL;
+	}
+
+	cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE);
+	nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
+	nsei = (uint16_t *) TLVP_VAL(&tp, NS_IE_NSEI);
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET (NSVCI=%u, cause=%s)\n",
+		nsvc->nsvci, nsvc->nsei, gprs_ns_cause_str(*cause));
+
+	/* Mark NS-VC as blocked and alive */
+	nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE;
+
+	nsvc->nsei = ntohs(*nsei);
+	nsvc->nsvci = ntohs(*nsvci);
+
+	/* start the test procedure */
+	gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
+
+	/* inform interested parties about the fact that this NSVC
+	 * has received RESET */
+	ns_dispatch_signal(nsvc, S_NS_RESET, *cause);
+
+	return gprs_ns_tx_reset_ack(nsvc);
+}
+
+static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct tlv_parsed tp;
+	uint8_t *cause;
+	int rc;
+
+	LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS BLOCK\n", nsvc->nsei);
+
+	nsvc->state |= NSE_S_BLOCKED;
+
+	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+			msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+	if (rc < 0) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS BLOCK "
+			"Error during TLV Parse\n", nsvc->nsei);
+		return rc;
+	}
+
+	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+	    !TLVP_PRESENT(&tp, NS_IE_VCI)) {
+		LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+		gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg);
+		return -EINVAL;
+	}
+
+	cause = (uint8_t *) TLVP_VAL(&tp, NS_IE_CAUSE);
+	//nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
+
+	ns_dispatch_signal(nsvc, S_NS_BLOCK, *cause);
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+
+	return gprs_ns_tx_simple(nsvc, NS_PDUT_BLOCK_ACK);
+}
+
+/* main entry point, here incoming NS frames enter */
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr, enum gprs_ns_ll ll)
+{
+	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+	struct gprs_nsvc *nsvc;
+	int rc = 0;
+
+	/* look up the NSVC based on source address */
+	nsvc = nsvc_by_rem_addr(nsi, saddr);
+	if (!nsvc) {
+		struct tlv_parsed tp;
+		uint16_t nsei;
+		if (nsh->pdu_type == NS_PDUT_STATUS) {
+			LOGP(DNS, LOGL_INFO, "Ignoring NS STATUS from %s:%u "
+			     "for non-existing NS-VC\n",
+			     inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port));
+			return 0;
+		}
+		/* Only the RESET procedure creates a new NSVC */
+		if (nsh->pdu_type != NS_PDUT_RESET) {
+			/* Since we have no NSVC, we have to use a fake */
+			nsvc = nsi->unknown_nsvc;
+			log_set_context(BSC_CTX_NSVC, nsvc);
+			LOGP(DNS, LOGL_INFO, "Rejecting NS PDU type 0x%0x "
+				"from %s:%u for non-existing NS-VC\n",
+				nsh->pdu_type, inet_ntoa(saddr->sin_addr),
+				ntohs(saddr->sin_port));
+			nsvc->nsvci = nsvc->nsei = 0xfffe;
+			nsvc->ip.bts_addr = *saddr;
+			nsvc->state = NSE_S_ALIVE;
+			nsvc->ll = ll;
+#if 0
+			return gprs_ns_tx_reset(nsvc, NS_CAUSE_PDU_INCOMP_PSTATE);
+#else
+			return gprs_ns_tx_status(nsvc,
+						NS_CAUSE_PDU_INCOMP_PSTATE, 0,
+						msg);
+#endif
+		}
+		rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data,
+				msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+		if (rc < 0) {
+			LOGP(DNS, LOGL_ERROR, "Rx NS RESET Error %d during "
+				"TLV Parse\n", rc);
+			return rc;
+		}
+		if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+		    !TLVP_PRESENT(&tp, NS_IE_VCI) ||
+		    !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+			LOGP(DNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+			gprs_ns_tx_status(nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0,
+					  msg);
+			return -EINVAL;
+		}
+		nsei = ntohs(*(uint16_t *)TLVP_VAL(&tp, NS_IE_NSEI));
+		/* Check if we already know this NSEI, the remote end might
+		 * simply have changed addresses, or it is a SGSN */
+		nsvc = nsvc_by_nsei(nsi, nsei);
+		if (!nsvc) {
+			nsvc = nsvc_create(nsi, 0xffff);
+			nsvc->ll = ll;
+			log_set_context(BSC_CTX_NSVC, nsvc);
+			LOGP(DNS, LOGL_INFO, "Creating NS-VC for BSS at %s:%u\n",
+				inet_ntoa(saddr->sin_addr), ntohs(saddr->sin_port));
+		}
+		/* Update the remote peer IP address/port */
+		nsvc->ip.bts_addr = *saddr;
+	} else
+		msgb_nsei(msg) = nsvc->nsei;
+
+	log_set_context(BSC_CTX_NSVC, nsvc);
+
+	/* Increment number of Incoming bytes */
+	rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_IN]);
+	rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg));
+
+	switch (nsh->pdu_type) {
+	case NS_PDUT_ALIVE:
+		/* If we're dead and blocked and suddenly receive a
+		 * NS-ALIVE out of the blue, we might have been re-started
+		 * and should send a NS-RESET to make sure everything recovers
+		 * fine. */
+		if (nsvc->state == NSE_S_BLOCKED)
+			rc = gprs_ns_tx_reset(nsvc, NS_CAUSE_PDU_INCOMP_PSTATE);
+		else
+			rc = gprs_ns_tx_alive_ack(nsvc);
+		break;
+	case NS_PDUT_ALIVE_ACK:
+		/* stop Tns-alive and start Tns-test */
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
+		if (nsvc->remote_end_is_sgsn) {
+			/* FIXME: this should be one level higher */
+			if (nsvc->state & NSE_S_BLOCKED)
+				rc = gprs_ns_tx_unblock(nsvc);
+		}
+		break;
+	case NS_PDUT_UNITDATA:
+		/* actual user data */
+		rc = gprs_ns_rx_unitdata(nsvc, msg);
+		break;
+	case NS_PDUT_STATUS:
+		rc = gprs_ns_rx_status(nsvc, msg);
+		break;
+	case NS_PDUT_RESET:
+		rc = gprs_ns_rx_reset(nsvc, msg);
+		break;
+	case NS_PDUT_RESET_ACK:
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET ACK\n", nsvc->nsei);
+		/* mark NS-VC as blocked + active */
+		nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+		if (nsvc->persistent || nsvc->remote_end_is_sgsn) {
+			/* stop RESET timer */
+			bsc_del_timer(&nsvc->timer);
+		}
+		/* Initiate TEST proc.: Send ALIVE and start timer */
+		rc = gprs_ns_tx_simple(nsvc, NS_PDUT_ALIVE);
+		nsvc_start_timer(nsvc, NSVC_TIMER_TNS_TEST);
+		break;
+	case NS_PDUT_UNBLOCK:
+		/* Section 7.2: unblocking procedure */
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS UNBLOCK\n", nsvc->nsei);
+		nsvc->state &= ~NSE_S_BLOCKED;
+		ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0);
+		rc = gprs_ns_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
+		break;
+	case NS_PDUT_UNBLOCK_ACK:
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS UNBLOCK ACK\n", nsvc->nsei);
+		/* mark NS-VC as unblocked + active */
+		nsvc->state = NSE_S_ALIVE;
+		nsvc->remote_state = NSE_S_ALIVE;
+		ns_dispatch_signal(nsvc, S_NS_UNBLOCK, 0);
+		break;
+	case NS_PDUT_BLOCK:
+		rc = gprs_ns_rx_block(nsvc, msg);
+		break;
+	case NS_PDUT_BLOCK_ACK:
+		LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS BLOCK ACK\n", nsvc->nsei);
+		/* mark remote NS-VC as blocked + active */
+		nsvc->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE;
+		break;
+	default:
+		LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx Unknown NS PDU type 0x%02x\n",
+			nsvc->nsei, nsh->pdu_type);
+		rc = -EINVAL;
+		break;
+	}
+	return rc;
+}
+
+struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb)
+{
+	struct gprs_ns_inst *nsi = talloc_zero(tall_bsc_ctx, struct gprs_ns_inst);
+
+	nsi->cb = cb;
+	INIT_LLIST_HEAD(&nsi->gprs_nsvcs);
+	nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
+	nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3;
+	nsi->timeout[NS_TOUT_TNS_RESET] = 3;
+	nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3;
+	nsi->timeout[NS_TOUT_TNS_TEST] = 30;
+	nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
+	nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
+
+	/* Create the dummy NSVC that we use for sending
+	 * messages to non-existant/unknown NS-VC's */
+	nsi->unknown_nsvc = nsvc_create(nsi, 0xfffe);
+	llist_del(&nsi->unknown_nsvc->list);
+
+	return nsi;
+}
+
+void gprs_ns_destroy(struct gprs_ns_inst *nsi)
+{
+	/* FIXME: clear all timers */
+
+	/* recursively free the NSI and all its NSVCs */
+	talloc_free(nsi);
+}
+
+
+/* NS-over-IP code, according to 3GPP TS 48.016 Chapter 6.2
+ * We don't support Size Procedure, Configuration Procedure, ChangeWeight Procedure */
+
+/* Read a single NS-over-IP message */
+static struct msgb *read_nsip_msg(struct bsc_fd *bfd, int *error,
+				  struct sockaddr_in *saddr)
+{
+	struct msgb *msg = gprs_ns_msgb_alloc();
+	int ret = 0;
+	socklen_t saddr_len = sizeof(*saddr);
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE - NS_ALLOC_HEADROOM, 0,
+			(struct sockaddr *)saddr, &saddr_len);
+	if (ret < 0) {
+		LOGP(DNS, LOGL_ERROR, "recv error %s during NSIP recv\n",
+			strerror(errno));
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	} else if (ret == 0) {
+		msgb_free(msg);
+		*error = ret;
+		return NULL;
+	}
+
+	msg->l2h = msg->data;
+	msgb_put(msg, ret);
+
+	return msg;
+}
+
+static int handle_nsip_read(struct bsc_fd *bfd)
+{
+	int error;
+	struct sockaddr_in saddr;
+	struct gprs_ns_inst *nsi = bfd->data;
+	struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+
+	if (!msg)
+		return error;
+
+	error = gprs_ns_rcvmsg(nsi, msg, &saddr, GPRS_NS_LL_UDP);
+
+	msgb_free(msg);
+
+	return error;
+}
+
+static int handle_nsip_write(struct bsc_fd *bfd)
+{
+	/* FIXME: actually send the data here instead of nsip_sendmsg() */
+	return -EIO;
+}
+
+static int nsip_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int rc;
+	struct gprs_ns_inst *nsi = nsvc->nsi;
+	struct sockaddr_in *daddr = &nsvc->ip.bts_addr;
+
+	rc = sendto(nsi->nsip.fd.fd, msg->data, msg->len, 0,
+		  (struct sockaddr *)daddr, sizeof(*daddr));
+
+	talloc_free(msg);
+
+	return rc;
+}
+
+/* UDP Port 23000 carries the LLC-in-BSSGP-in-NS protocol stack */
+static int nsip_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_nsip_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_nsip_write(bfd);
+
+	return rc;
+}
+
+/* Listen for incoming GPRS packets */
+int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
+{
+	int ret;
+
+	ret = make_sock(&nsi->nsip.fd, IPPROTO_UDP, nsi->nsip.local_ip,
+			nsi->nsip.local_port, nsip_fd_cb);
+	if (ret < 0)
+		return ret;
+
+	nsi->nsip.fd.data = nsi;
+
+	return ret;
+}
+
+/* Initiate a RESET procedure */
+void gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause)
+{
+	LOGP(DNS, LOGL_INFO, "NSEI=%u RESET procedure based on API request\n",
+		nsvc->nsei);
+
+	/* Mark NS-VC locally as blocked and dead */
+	nsvc->state = NSE_S_BLOCKED;
+	/* Send NS-RESET PDU */
+	if (gprs_ns_tx_reset(nsvc, cause) < 0) {
+		LOGP(DNS, LOGL_ERROR, "NSEI=%u, error resetting NS-VC\n",
+			nsvc->nsei);
+	}
+	/* Start Tns-reset */
+	nsvc_start_timer(nsvc, NSVC_TIMER_TNS_RESET);
+}
+
+/* Establish a connection (from the BSS) to the SGSN */
+struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi,
+				struct sockaddr_in *dest, uint16_t nsei,
+				uint16_t nsvci)
+{
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_rem_addr(nsi, dest);
+	if (!nsvc)
+		nsvc = nsvc_create(nsi, nsvci);
+	nsvc->ip.bts_addr = *dest;
+	nsvc->nsei = nsei;
+	nsvc->nsvci = nsvci;
+	nsvc->remote_end_is_sgsn = 1;
+
+	gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+	return nsvc;
+}
diff --git a/src/libgb/gprs_ns_frgre.c b/src/libgb/gprs_ns_frgre.c
new file mode 100644
index 0000000..106f410
--- /dev/null
+++ b/src/libgb/gprs_ns_frgre.c
@@ -0,0 +1,304 @@
+/* GPRS Networks Service (NS) messages on the Gb interface
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */
+
+/* NS-over-FR-over-GRE implementation */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+
+#include <openbsc/socket.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_ns.h>
+
+#define GRE_PTYPE_FR	0x6559
+#define GRE_PTYPE_IPv4	0x0800
+#define GRE_PTYPE_KAR	0x0000	/* keepalive response */
+
+struct gre_hdr {
+	uint16_t flags;
+	uint16_t ptype;
+} __attribute__ ((packed));
+
+/* IPv4 messages inside the GRE tunnel might be GRE keepalives */
+static int handle_rx_gre_ipv4(struct bsc_fd *bfd, struct msgb *msg,
+				struct iphdr *iph, struct gre_hdr *greh)
+{
+	struct gprs_ns_inst *nsi = bfd->data;
+	int gre_payload_len;
+	struct iphdr *inner_iph;
+	struct gre_hdr *inner_greh;
+	struct sockaddr_in daddr;
+	struct in_addr ia;
+
+	gre_payload_len = msg->len - (iph->ihl*4 + sizeof(*greh));
+
+	inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh));
+
+	if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) {
+		LOGP(DNS, LOGL_ERROR, "GRE keepalive too short\n");
+		return -EIO;
+	}
+
+	if (inner_iph->saddr != iph->daddr ||
+	    inner_iph->daddr != iph->saddr) {
+		LOGP(DNS, LOGL_ERROR,
+			"GRE keepalive with wrong tunnel addresses\n");
+		return -EIO;
+	}
+
+	if (inner_iph->protocol != IPPROTO_GRE) {
+		LOGP(DNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+		return -EIO;
+	}
+
+	inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4);
+	if (inner_greh->ptype != htons(GRE_PTYPE_KAR)) {
+		LOGP(DNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+		return -EIO;
+	}
+
+	/* Actually send the response back */
+
+	daddr.sin_family = AF_INET;
+	daddr.sin_addr.s_addr = inner_iph->daddr;
+	daddr.sin_port = IPPROTO_GRE;
+
+	ia.s_addr = iph->saddr;
+	LOGP(DNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n",
+		inet_ntoa(ia));
+
+	return sendto(nsi->frgre.fd.fd, inner_greh,
+		      gre_payload_len - inner_iph->ihl*4, 0,
+		      (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+static struct msgb *read_nsfrgre_msg(struct bsc_fd *bfd, int *error,
+					struct sockaddr_in *saddr)
+{
+	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
+	int ret = 0;
+	socklen_t saddr_len = sizeof(*saddr);
+	struct iphdr *iph;
+	struct gre_hdr *greh;
+	uint8_t *frh;
+	uint16_t dlci;
+
+	if (!msg) {
+		*error = -ENOMEM;
+		return NULL;
+	}
+
+	ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
+			(struct sockaddr *)saddr, &saddr_len);
+	if (ret < 0) {
+		LOGP(DNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n",
+			strerror(errno));
+		*error = ret;
+		goto out_err;
+	} else if (ret == 0) {
+		*error = ret;
+		goto out_err;
+	}
+
+	msgb_put(msg, ret);
+
+	if (msg->len < sizeof(*iph) + sizeof(*greh) + 2) {
+		LOGP(DNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+		*error = -EIO;
+		goto out_err;
+	}
+
+	iph = (struct iphdr *) msg->data;
+	if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) {
+		LOGP(DNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+		*error = -EIO;
+		goto out_err;
+	}
+
+	greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+	if (greh->flags) {
+		LOGP(DNS, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n",
+			ntohs(greh->flags));
+	}
+
+	switch (ntohs(greh->ptype)) {
+	case GRE_PTYPE_IPv4:
+		/* IPv4 messages might be GRE keepalives */
+		*error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+		goto out_err;
+		break;
+	case GRE_PTYPE_FR:
+		/* continue as usual */
+		break;
+	default:
+		LOGP(DNS, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n",
+			ntohs(greh->ptype));
+		*error = -EIO;
+		goto out_err;
+		break;
+	}
+
+	if (msg->len < sizeof(*greh) + 2) {
+		LOGP(DNS, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
+		*error = -EIO;
+		goto out_err;
+	}
+
+	frh = (uint8_t *)greh + sizeof(*greh);
+	if (frh[0] & 0x01) {
+		LOGP(DNS, LOGL_NOTICE, "Unsupported single-byte FR address\n");
+		*error = -EIO;
+		goto out_err;
+	}
+	dlci = ((frh[0] & 0xfc) << 2);
+	if ((frh[1] & 0x0f) != 0x01) {
+		LOGP(DNS, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n",
+			frh[1]);
+		*error = -EIO;
+		goto out_err;
+	}
+	dlci |= (frh[1] >> 4);
+
+	msg->l2h = frh+2;
+
+	/* Store DLCI in NETWORK BYTEORDER in sockaddr port member */
+	saddr->sin_port = htons(dlci);
+
+	return msg;
+
+out_err:
+	msgb_free(msg);
+	return NULL;
+}
+
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+		   struct sockaddr_in *saddr, enum gprs_ns_ll ll);
+
+static int handle_nsfrgre_read(struct bsc_fd *bfd)
+{
+	int rc;
+	struct sockaddr_in saddr;
+	struct gprs_ns_inst *nsi = bfd->data;
+	struct msgb *msg;
+	uint16_t dlci;
+
+	msg = read_nsfrgre_msg(bfd, &rc, &saddr);
+	if (!msg)
+		return rc;
+
+	dlci = ntohs(saddr.sin_port);
+	if (dlci == 0 || dlci == 1023) {
+		LOGP(DNS, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n",
+			dlci);
+		rc = 0;
+		goto out;
+	}
+
+	rc = gprs_ns_rcvmsg(nsi, msg, &saddr, GPRS_NS_LL_FR_GRE);
+out:
+	msgb_free(msg);
+
+	return rc;
+}
+
+static int handle_nsfrgre_write(struct bsc_fd *bfd)
+{
+	/* FIXME: actually send the data here instead of nsip_sendmsg() */
+	return -EIO;
+}
+
+int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg)
+{
+	int rc;
+	struct gprs_ns_inst *nsi = nsvc->nsi;
+	struct sockaddr_in daddr;
+	uint16_t dlci = ntohs(nsvc->frgre.bts_addr.sin_port);
+	uint8_t *frh;
+	struct gre_hdr *greh;
+
+	/* Build socket address for the packet destionation */
+	daddr.sin_family = AF_INET;
+	daddr.sin_addr = nsvc->frgre.bts_addr.sin_addr;
+	daddr.sin_port = IPPROTO_GRE;
+
+	/* Prepend the FR header */
+	frh = msgb_push(msg, 2);
+	frh[0] = (dlci >> 2) & 0xfc;
+	frh[1] = ((dlci & 0xf)<<4) | 0x01;
+
+	/* Prepend the GRE header */
+	greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh));
+	greh->flags = 0;
+	greh->ptype = htons(GRE_PTYPE_FR);
+
+	rc = sendto(nsi->frgre.fd.fd, msg->data, msg->len, 0,
+		  (struct sockaddr *)&daddr, sizeof(daddr));
+
+	talloc_free(msg);
+
+	return rc;
+}
+
+static int nsfrgre_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_nsfrgre_read(bfd);
+	if (what & BSC_FD_WRITE)
+		rc = handle_nsfrgre_write(bfd);
+
+	return rc;
+}
+
+int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi)
+{
+	int rc;
+
+	/* Make sure we close any existing socket before changing it */
+	if (nsi->frgre.fd.fd)
+		close(nsi->frgre.fd.fd);
+
+	if (!nsi->frgre.enabled)
+		return 0;
+
+	rc = make_sock(&nsi->frgre.fd, IPPROTO_GRE, nsi->frgre.local_ip,
+			0, nsfrgre_fd_cb);
+	if (rc < 0) {
+		LOGP(DNS, LOGL_ERROR, "Error creating GRE socket (%s)\n",
+			strerror(errno));
+		return rc;
+	}
+	nsi->frgre.fd.data = nsi;
+
+	return rc;
+}
diff --git a/src/libgb/gprs_ns_vty.c b/src/libgb/gprs_ns_vty.c
new file mode 100644
index 0000000..39277fc
--- /dev/null
+++ b/src/libgb/gprs_ns_vty.c
@@ -0,0 +1,569 @@
+/* VTY interface for our GPRS Networks Service (NS) implementation */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+static struct gprs_ns_inst *vty_nsi = NULL;
+
+/* FIXME: this should go to some common file as it is copied
+ * in vty_interface.c of the BSC */
+static const struct value_string gprs_ns_timer_strs[] = {
+	{ 0, "tns-block" },
+	{ 1, "tns-block-retries" },
+	{ 2, "tns-reset" },
+	{ 3, "tns-reset-retries" },
+	{ 4, "tns-test" },
+	{ 5, "tns-alive" },
+	{ 6, "tns-alive-retries" },
+	{ 0, NULL }
+};
+
+static struct cmd_node ns_node = {
+	NS_NODE,
+	"%s(ns)#",
+	1,
+};
+
+static int config_write_ns(struct vty *vty)
+{
+	struct gprs_nsvc *nsvc;
+	unsigned int i;
+	struct in_addr ia;
+
+	vty_out(vty, "ns%s", VTY_NEWLINE);
+
+	llist_for_each_entry(nsvc, &vty_nsi->gprs_nsvcs, list) {
+		if (!nsvc->persistent)
+			continue;
+		vty_out(vty, " nse %u nsvci %u%s",
+			nsvc->nsei, nsvc->nsvci, VTY_NEWLINE);
+		vty_out(vty, " nse %u remote-role %s%s",
+			nsvc->nsei, nsvc->remote_end_is_sgsn ? "sgsn" : "bss",
+			VTY_NEWLINE);
+		switch (nsvc->ll) {
+		case GPRS_NS_LL_UDP:
+			vty_out(vty, " nse %u encapsulation udp%s", nsvc->nsei,
+				VTY_NEWLINE);
+			vty_out(vty, " nse %u remote-ip %s%s",
+				nsvc->nsei,
+				inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+				VTY_NEWLINE);
+			vty_out(vty, " nse %u remote-port %u%s",
+				nsvc->nsei, ntohs(nsvc->ip.bts_addr.sin_port),
+				VTY_NEWLINE);
+			break;
+		case GPRS_NS_LL_FR_GRE:
+			vty_out(vty, " nse %u encapsulation framerelay-gre%s",
+				nsvc->nsei, VTY_NEWLINE);
+			vty_out(vty, " nse %u remote-ip %s%s",
+				nsvc->nsei,
+				inet_ntoa(nsvc->frgre.bts_addr.sin_addr),
+				VTY_NEWLINE);
+			vty_out(vty, " nse %u fr-dlci %u%s",
+				nsvc->nsei, ntohs(nsvc->frgre.bts_addr.sin_port),
+				VTY_NEWLINE);
+		default:
+			break;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++)
+		vty_out(vty, " timer %s %u%s",
+			get_value_string(gprs_ns_timer_strs, i),
+			vty_nsi->timeout[i], VTY_NEWLINE);
+
+	if (vty_nsi->nsip.local_ip) {
+		ia.s_addr = htonl(vty_nsi->nsip.local_ip);
+		vty_out(vty, " encapsulation udp local-ip %s%s",
+			inet_ntoa(ia), VTY_NEWLINE);
+	}
+	if (vty_nsi->nsip.local_port)
+		vty_out(vty, " encapsulation udp local-port %u%s",
+			vty_nsi->nsip.local_port, VTY_NEWLINE);
+
+	vty_out(vty, " encapsulation framerelay-gre enabled %u%s",
+		vty_nsi->frgre.enabled ? 1 : 0, VTY_NEWLINE);
+	if (vty_nsi->frgre.local_ip) {
+		ia.s_addr = htonl(vty_nsi->frgre.local_ip);
+		vty_out(vty, " encapsulation framerelay-gre local-ip %s%s",
+			inet_ntoa(ia), VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns, cfg_ns_cmd,
+      "ns",
+      "Configure the GPRS Network Service")
+{
+	vty->node = NS_NODE;
+	return CMD_SUCCESS;
+}
+
+static void dump_nse(struct vty *vty, struct gprs_nsvc *nsvc, int stats)
+{
+	vty_out(vty, "NSEI %5u, NS-VC %5u, Remote: %-4s, %5s %9s",
+		nsvc->nsei, nsvc->nsvci,
+		nsvc->remote_end_is_sgsn ? "SGSN" : "BSS",
+		nsvc->state & NSE_S_ALIVE ? "ALIVE" : "DEAD",
+		nsvc->state & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED");
+	if (nsvc->ll == GPRS_NS_LL_UDP || nsvc->ll == GPRS_NS_LL_FR_GRE)
+		vty_out(vty, ", %s %15s:%u",
+			nsvc->ll == GPRS_NS_LL_UDP ? "UDP   " : "FR-GRE",
+			inet_ntoa(nsvc->ip.bts_addr.sin_addr),
+			ntohs(nsvc->ip.bts_addr.sin_port));
+	vty_out(vty, "%s", VTY_NEWLINE);
+	if (stats)
+		vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+}
+
+static void dump_ns(struct vty *vty, struct gprs_ns_inst *nsi, int stats)
+{
+	struct gprs_nsvc *nsvc;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(vty_nsi->nsip.local_ip);
+	vty_out(vty, "Encapsulation NS-UDP-IP     Local IP: %s, UDP Port: %u%s",
+		inet_ntoa(ia), vty_nsi->nsip.local_port, VTY_NEWLINE);
+
+	ia.s_addr = htonl(vty_nsi->frgre.local_ip);
+	vty_out(vty, "Encapsulation NS-FR-GRE-IP  Local IP: %s%s",
+		inet_ntoa(ia), VTY_NEWLINE);
+
+	llist_for_each_entry(nsvc, &nsi->gprs_nsvcs, list) {
+		if (nsvc == nsi->unknown_nsvc)
+			continue;
+		dump_nse(vty, nsvc, stats);
+	}
+}
+
+DEFUN(show_ns, show_ns_cmd, "show ns",
+	SHOW_STR "Display information about the NS protocol")
+{
+	struct gprs_ns_inst *nsi = vty_nsi;
+	dump_ns(vty, nsi, 0);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_stats, show_ns_stats_cmd, "show ns stats",
+	SHOW_STR
+	"Display information about the NS protocol\n"
+	"Include statistics\n")
+{
+	struct gprs_ns_inst *nsi = vty_nsi;
+	dump_ns(vty, nsi, 1);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
+	SHOW_STR "Display information about the NS protocol\n"
+	"Select one NSE by its NSE Identifier\n"
+	"Select one NSE by its NS-VC Identifier\n"
+	"The Identifier of selected type\n"
+	"Include Statistics\n")
+{
+	struct gprs_ns_inst *nsi = vty_nsi;
+	struct gprs_nsvc *nsvc;
+	uint16_t id = atoi(argv[1]);
+	int show_stats = 0;
+
+	if (!strcmp(argv[0], "nsei"))
+		nsvc = nsvc_by_nsei(nsi, id);
+	else
+		nsvc = nsvc_by_nsvci(nsi, id);
+
+	if (!nsvc) {
+		vty_out(vty, "No such NS Entity%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (argc >= 3)
+		show_stats = 1;
+
+	dump_nse(vty, nsvc, show_stats);
+	return CMD_SUCCESS;
+}
+
+#define NSE_CMD_STR "Persistent NS Entity\n" "NS Entity ID (NSEI)\n"
+
+DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd,
+	"nse <0-65535> nsvci <0-65534>",
+	NSE_CMD_STR
+	"NS Virtual Connection\n"
+	"NS Virtual Connection ID (NSVCI)\n"
+	)
+{
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t nsvci = atoi(argv[1]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		nsvc = nsvc_create(vty_nsi, nsvci);
+		nsvc->nsei = nsei;
+	}
+	nsvc->nsvci = nsvci;
+	/* All NSVCs that are explicitly configured by VTY are
+	 * marked as persistent so we can write them to the config
+	 * file at some later point */
+	nsvc->persistent = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_remoteip, cfg_nse_remoteip_cmd,
+	"nse <0-65535> remote-ip A.B.C.D",
+	NSE_CMD_STR
+	"Remote IP Address\n"
+	"Remote IP Address\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	inet_aton(argv[1], &nsvc->ip.bts_addr.sin_addr);
+
+	return CMD_SUCCESS;
+
+}
+
+DEFUN(cfg_nse_remoteport, cfg_nse_remoteport_cmd,
+	"nse <0-65535> remote-port <0-65535>",
+	NSE_CMD_STR
+	"Remote UDP Port\n"
+	"Remote UDP Port Number\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t port = atoi(argv[1]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (nsvc->ll != GPRS_NS_LL_UDP) {
+		vty_out(vty, "Cannot set UDP Port on non-UDP NSE%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nsvc->ip.bts_addr.sin_port = htons(port);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd,
+	"nse <0-65535> fr-dlci <16-1007>",
+	NSE_CMD_STR
+	"Frame Relay DLCI\n"
+	"Frame Relay DLCI Number\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	uint16_t dlci = atoi(argv[1]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (nsvc->ll != GPRS_NS_LL_FR_GRE) {
+		vty_out(vty, "Cannot set FR DLCI on non-FR NSE%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nsvc->frgre.bts_addr.sin_port = htons(dlci);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nse_encaps, cfg_nse_encaps_cmd,
+	"nse <0-65535> encapsulation (udp|framerelay-gre)",
+	NSE_CMD_STR
+	"Encapsulation for NS\n"
+	"UDP/IP Encapsulation\n" "Frame-Relay/GRE/IP Encapsulation\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[1], "udp"))
+		nsvc->ll = GPRS_NS_LL_UDP;
+	else
+		nsvc->ll = GPRS_NS_LL_FR_GRE;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd,
+	"nse <0-65535> remote-role (sgsn|bss)",
+	NSE_CMD_STR
+	"Remote NSE Role\n"
+	"Remote Peer is SGSN\n"
+	"Remote Peer is BSS\n")
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[1], "sgsn"))
+		nsvc->remote_end_is_sgsn = 1;
+	else
+		nsvc->remote_end_is_sgsn = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_nse, cfg_no_nse_cmd,
+	"no nse <0-65535>",
+	"Delete Persistent NS Entity\n"
+	"Delete " NSE_CMD_STR)
+{
+	uint16_t nsei = atoi(argv[0]);
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsei);
+	if (!nsvc) {
+		vty_out(vty, "No such NSE (%u)%s", nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!nsvc->persistent) {
+		vty_out(vty, "NSEI %u is not a persistent NSE%s",
+			nsei, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	nsvc->persistent = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
+	"timer " NS_TIMERS " <0-65535>",
+	"Network Service Timer\n"
+	NS_TIMERS_HELP "Timer Value\n")
+{
+	int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+	int val = atoi(argv[1]);
+
+	if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+		return CMD_WARNING;
+
+	vty_nsi->timeout[idx] = val;
+
+	return CMD_SUCCESS;
+}
+
+#define ENCAPS_STR "NS encapsulation options\n"
+
+DEFUN(cfg_nsip_local_ip, cfg_nsip_local_ip_cmd,
+      "encapsulation udp local-ip A.B.C.D",
+	ENCAPS_STR "NS over UDP Encapsulation\n"
+	"Set the IP address on which we listen for NS/UDP\n"
+	"IP Address\n")
+{
+	struct in_addr ia;
+
+	inet_aton(argv[0], &ia);
+	vty_nsi->nsip.local_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nsip_local_port, cfg_nsip_local_port_cmd,
+      "encapsulation udp local-port <0-65535>",
+	ENCAPS_STR "NS over UDP Encapsulation\n"
+	"Set the UDP port on which we listen for NS/UDP\n"
+	"UDP port number\n")
+{
+	unsigned int port = atoi(argv[0]);
+
+	vty_nsi->nsip.local_port = port;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_frgre_local_ip, cfg_frgre_local_ip_cmd,
+      "encapsulation framerelay-gre local-ip A.B.C.D",
+	ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
+	"Set the IP address on which we listen for NS/FR/GRE\n"
+	"IP Address\n")
+{
+	struct in_addr ia;
+
+	if (!vty_nsi->frgre.enabled) {
+		vty_out(vty, "FR/GRE is not enabled%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	inet_aton(argv[0], &ia);
+	vty_nsi->frgre.local_ip = ntohl(ia.s_addr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_frgre_enable, cfg_frgre_enable_cmd,
+      "encapsulation framerelay-gre enabled (1|0)",
+	ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
+	"Enable or disable Frame Relay over GRE\n"
+	"Enable\n" "Disable\n")
+{
+	int enabled = atoi(argv[0]);
+
+	vty_nsi->frgre.enabled = enabled;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(nsvc_nsei, nsvc_nsei_cmd,
+	"nsvc nsei <0-65535> (block|unblock|reset)",
+	"Perform an operation on a NSVC\n"
+	"NS-VC Identifier (NS-VCI)\n"
+	"Initiate BLOCK procedure\n"
+	"Initiate UNBLOCK procedure\n"
+	"Initiate RESET procedure\n")
+{
+	uint16_t nsvci = atoi(argv[0]);
+	const char *operation = argv[1];
+	struct gprs_nsvc *nsvc;
+
+	nsvc = nsvc_by_nsei(vty_nsi, nsvci);
+	if (!nsvc) {
+		vty_out(vty, "No such NSVCI (%u)%s", nsvci, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(operation, "block"))
+		gprs_ns_tx_block(nsvc, NS_CAUSE_OM_INTERVENTION);
+	else if (!strcmp(operation, "unblock"))
+		gprs_ns_tx_unblock(nsvc);
+	else if (!strcmp(operation, "reset"))
+		gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION);
+	else
+		return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_nsvc,
+      logging_fltr_nsvc_cmd,
+      "logging filter nsvc (nsei|nsvci) <0-65535>",
+	LOGGING_STR FILTER_STR
+	"Filter based on NS Virtual Connection\n"
+	"Identify NS-VC by NSEI\n"
+	"Identify NS-VC by NSVCI\n"
+	"Numeric identifier\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+	struct gprs_nsvc *nsvc;
+	uint16_t id = atoi(argv[1]);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	if (!strcmp(argv[0], "nsei"))
+		nsvc = nsvc_by_nsei(vty_nsi, id);
+	else
+		nsvc = nsvc_by_nsvci(vty_nsi, id);
+
+	if (!nsvc) {
+		vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_set_nsvc_filter(tgt, nsvc);
+	return CMD_SUCCESS;
+}
+
+int gprs_ns_vty_init(struct gprs_ns_inst *nsi)
+{
+	vty_nsi = nsi;
+
+	install_element_ve(&show_ns_cmd);
+	install_element_ve(&show_ns_stats_cmd);
+	install_element_ve(&show_nse_cmd);
+	install_element_ve(&logging_fltr_nsvc_cmd);
+
+	install_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
+
+	install_element(CONFIG_NODE, &cfg_ns_cmd);
+	install_node(&ns_node, config_write_ns);
+	install_default(NS_NODE);
+	install_element(NS_NODE, &ournode_exit_cmd);
+	install_element(NS_NODE, &ournode_end_cmd);
+	install_element(NS_NODE, &cfg_nse_nsvci_cmd);
+	install_element(NS_NODE, &cfg_nse_remoteip_cmd);
+	install_element(NS_NODE, &cfg_nse_remoteport_cmd);
+	install_element(NS_NODE, &cfg_nse_fr_dlci_cmd);
+	install_element(NS_NODE, &cfg_nse_encaps_cmd);
+	install_element(NS_NODE, &cfg_nse_remoterole_cmd);
+	install_element(NS_NODE, &cfg_no_nse_cmd);
+	install_element(NS_NODE, &cfg_ns_timer_cmd);
+	install_element(NS_NODE, &cfg_nsip_local_ip_cmd);
+	install_element(NS_NODE, &cfg_nsip_local_port_cmd);
+	install_element(NS_NODE, &cfg_frgre_enable_cmd);
+	install_element(NS_NODE, &cfg_frgre_local_ip_cmd);
+
+	install_element(ENABLE_NODE, &nsvc_nsei_cmd);
+
+	return 0;
+}
diff --git a/src/libmgcp/Makefile.am b/src/libmgcp/Makefile.am
new file mode 100644
index 0000000..b1d1d15
--- /dev/null
+++ b/src/libmgcp/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libmgcp.a
+
+libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c
diff --git a/src/libmgcp/Makefile.in b/src/libmgcp/Makefile.in
new file mode 100644
index 0000000..942aeea
--- /dev/null
+++ b/src/libmgcp/Makefile.in
@@ -0,0 +1,453 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libmgcp
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libmgcp_a_AR = $(AR) $(ARFLAGS)
+libmgcp_a_LIBADD =
+am_libmgcp_a_OBJECTS = mgcp_protocol.$(OBJEXT) mgcp_network.$(OBJEXT) \
+	mgcp_vty.$(OBJEXT)
+libmgcp_a_OBJECTS = $(am_libmgcp_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libmgcp_a_SOURCES)
+DIST_SOURCES = $(libmgcp_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libmgcp.a
+libmgcp_a_SOURCES = mgcp_protocol.c mgcp_network.c mgcp_vty.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libmgcp/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libmgcp/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libmgcp.a: $(libmgcp_a_OBJECTS) $(libmgcp_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libmgcp.a
+	$(AM_V_AR)$(libmgcp_a_AR) libmgcp.a $(libmgcp_a_OBJECTS) $(libmgcp_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libmgcp.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_network.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_protocol.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_vty.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libmgcp/mgcp_network.c b/src/libmgcp/mgcp_network.c
new file mode 100644
index 0000000..51c08d1
--- /dev/null
+++ b/src/libmgcp/mgcp_network.c
@@ -0,0 +1,579 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/select.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#warning "Make use of the rtp proxy code"
+
+/* attempt to determine byte order */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <limits.h>
+
+#ifndef __BYTE_ORDER
+#error "__BYTE_ORDER should be defined by someone"
+#endif
+
+/* according to rtp_proxy.c RFC 3550 */
+struct rtp_hdr {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	uint8_t  csrc_count:4,
+		  extension:1,
+		  padding:1,
+		  version:2;
+	uint8_t  payload_type:7,
+		  marker:1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	uint8_t  version:2,
+		  padding:1,
+		  extension:1,
+		  csrc_count:4;
+	uint8_t  marker:1,
+		  payload_type:7;
+#endif
+	uint16_t sequence;
+	uint32_t timestamp;
+	uint32_t ssrc;
+} __attribute__((packed));
+
+
+enum {
+	DEST_NETWORK = 0,
+	DEST_BTS = 1,
+};
+
+enum {
+	PROTO_RTP,
+	PROTO_RTCP,
+};
+
+#define DUMMY_LOAD 0x23
+
+
+static int udp_send(int fd, struct in_addr *addr, int port, char *buf, int len)
+{
+	struct sockaddr_in out;
+	out.sin_family = AF_INET;
+	out.sin_port = port;
+	memcpy(&out.sin_addr, addr, sizeof(*addr));
+
+	return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out));
+}
+
+int mgcp_send_dummy(struct mgcp_endpoint *endp)
+{
+	static char buf[] = { DUMMY_LOAD };
+
+	return udp_send(endp->net_end.rtp.fd, &endp->net_end.addr,
+			endp->net_end.rtp_port, buf, 1);
+}
+
+static void patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state,
+			    int payload, struct sockaddr_in *addr, char *data, int len)
+{
+	uint16_t seq;
+	uint32_t timestamp;
+	struct rtp_hdr *rtp_hdr;
+
+	if (len < sizeof(*rtp_hdr))
+		return;
+
+	rtp_hdr = (struct rtp_hdr *) data;
+	seq = ntohs(rtp_hdr->sequence);
+	timestamp = ntohl(rtp_hdr->timestamp);
+
+	if (!state->initialized) {
+		state->seq_no = seq - 1;
+		state->ssrc = state->orig_ssrc = rtp_hdr->ssrc;
+		state->initialized = 1;
+		state->last_timestamp = timestamp;
+	} else if (state->ssrc != rtp_hdr->ssrc) {
+		state->ssrc = rtp_hdr->ssrc;
+		state->seq_offset = (state->seq_no + 1) - seq;
+		state->timestamp_offset = state->last_timestamp - timestamp;
+		state->patch = endp->allow_patch;
+		LOGP(DMGCP, LOGL_NOTICE,
+			"The SSRC changed on 0x%x SSRC: %u offset: %d from %s:%d in %d\n",
+			ENDPOINT_NUMBER(endp), state->ssrc, state->seq_offset,
+			inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), endp->conn_mode);
+	}
+
+	/* apply the offset and store it back to the packet */
+	if (state->patch) {
+		seq += state->seq_offset;
+		rtp_hdr->sequence = htons(seq);
+		rtp_hdr->ssrc = state->orig_ssrc;
+
+		timestamp += state->timestamp_offset;
+		rtp_hdr->timestamp = htonl(timestamp);
+	}
+
+	/* seq changed, now compare if we have lost something */
+	if (state->seq_no + 1u != seq)
+		state->lost_no = abs(seq - (state->seq_no + 1));
+	state->seq_no = seq;
+
+	state->last_timestamp = timestamp;
+
+	if (payload < 0)
+		return;
+
+	rtp_hdr->payload_type = payload;
+}
+
+/*
+ * The below code is for dispatching. We have a dedicated port for
+ * the data coming from the net and one to discover the BTS.
+ */
+static int forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, int len)
+{
+	if (!tap->enabled)
+		return 0;
+
+	return sendto(fd, buf, len, 0,
+		      (struct sockaddr *)&tap->forward, sizeof(tap->forward));
+}
+
+static int send_transcoder(struct mgcp_rtp_end *end, struct mgcp_config *cfg,
+			   int is_rtp, const char *buf, int len)
+{
+	int rc;
+	int port;
+	struct sockaddr_in addr;
+
+	port = is_rtp ? end->rtp_port : end->rtcp_port;
+
+	addr.sin_family = AF_INET;
+	addr.sin_addr = cfg->transcoder_in;
+	addr.sin_port = port;
+
+	rc = sendto(is_rtp ?
+		end->rtp.fd :
+		end->rtcp.fd, buf, len, 0,
+		(struct sockaddr *) &addr, sizeof(addr));
+
+	if (rc != len)
+		LOGP(DMGCP, LOGL_ERROR,
+			"Failed to send data to the transcoder: %s\n",
+			strerror(errno));
+
+	return rc;
+}
+
+static int send_to(struct mgcp_endpoint *endp, int dest, int is_rtp,
+		   struct sockaddr_in *addr, char *buf, int rc)
+{
+	struct mgcp_trunk_config *tcfg = endp->tcfg;
+	/* For loop toggle the destination and then dispatch. */
+	if (tcfg->audio_loop)
+		dest = !dest;
+
+	/* Loop based on the conn_mode, maybe undoing the above */
+	if (endp->conn_mode == MGCP_CONN_LOOPBACK)
+		dest = !dest;
+
+	if (dest == DEST_NETWORK) {
+		if (is_rtp) {
+			patch_and_count(endp, &endp->bts_state,
+					endp->net_end.payload_type,
+					addr, buf, rc);
+			forward_data(endp->net_end.rtp.fd,
+				     &endp->taps[MGCP_TAP_NET_OUT], buf, rc);
+			return udp_send(endp->net_end.rtp.fd, &endp->net_end.addr,
+					endp->net_end.rtp_port, buf, rc);
+		} else {
+			return udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr,
+					endp->net_end.rtcp_port, buf, rc);
+		}
+	} else {
+		if (is_rtp) {
+			patch_and_count(endp, &endp->net_state,
+					endp->bts_end.payload_type,
+					addr, buf, rc);
+			forward_data(endp->bts_end.rtp.fd,
+				     &endp->taps[MGCP_TAP_BTS_OUT], buf, rc);
+			return udp_send(endp->bts_end.rtp.fd, &endp->bts_end.addr,
+					endp->bts_end.rtp_port, buf, rc);
+		} else {
+			return udp_send(endp->bts_end.rtcp.fd, &endp->bts_end.addr,
+					endp->bts_end.rtcp_port, buf, rc);
+		}
+	}
+}
+
+static int recevice_from(struct mgcp_endpoint *endp, int fd, struct sockaddr_in *addr,
+			 char *buf, int bufsize)
+{
+	int rc;
+	socklen_t slen = sizeof(*addr);
+
+	rc = recvfrom(fd, buf, bufsize, 0,
+			    (struct sockaddr *) addr, &slen);
+	if (rc < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n",
+			ENDPOINT_NUMBER(endp), errno, strerror(errno));
+		return -1;
+	}
+
+	/* do not forward aynthing... maybe there is a packet from the bts */
+	if (!endp->allocated)
+		return -1;
+
+	#warning "Slight spec violation. With connection mode recvonly we should attempt to forward."
+
+	return rc;
+}
+
+static int rtp_data_net(struct bsc_fd *fd, unsigned int what)
+{
+	char buf[4096];
+	struct sockaddr_in addr;
+	struct mgcp_endpoint *endp;
+	int rc, proto;
+
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	rc = recevice_from(endp, fd->fd, &addr, buf, sizeof(buf));
+	if (rc <= 0)
+		return -1;
+
+	if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong address %s on 0x%x\n",
+			inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp));
+		return -1;
+	}
+
+	if (endp->net_end.rtp_port != addr.sin_port &&
+	    endp->net_end.rtcp_port != addr.sin_port) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong source port %d on 0x%x\n",
+			ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
+		return -1;
+	}
+
+	/* throw away the dummy message */
+	if (rc == 1 && buf[0] == DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	proto = fd == &endp->net_end.rtp ? PROTO_RTP : PROTO_RTCP;
+	endp->net_end.packets += 1;
+
+	forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc);
+	if (endp->is_transcoded)
+		return send_transcoder(&endp->trans_net, endp->cfg, proto == PROTO_RTP, &buf[0], rc);
+	else
+		return send_to(endp, DEST_BTS, proto == PROTO_RTP, &addr, &buf[0], rc);
+}
+
+static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr)
+{
+	struct mgcp_config *cfg = endp->cfg;
+
+	if (proto == PROTO_RTP && endp->bts_end.rtp_port == 0) {
+		if (!cfg->bts_ip ||
+		    memcmp(&addr->sin_addr,
+			   &cfg->bts_in, sizeof(cfg->bts_in)) == 0 ||
+		    memcmp(&addr->sin_addr,
+			   &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) {
+
+			endp->bts_end.rtp_port = addr->sin_port;
+			endp->bts_end.addr = addr->sin_addr;
+
+			LOGP(DMGCP, LOGL_NOTICE,
+				"Found BTS for endpoint: 0x%x on port: %d/%d of %s\n",
+				ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port),
+				ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr));
+		}
+	} else if (proto == PROTO_RTCP && endp->bts_end.rtcp_port == 0) {
+		if (memcmp(&endp->bts_end.addr, &addr->sin_addr,
+				sizeof(endp->bts_end.addr)) == 0) {
+			endp->bts_end.rtcp_port = addr->sin_port;
+		}
+	}
+}
+
+static int rtp_data_bts(struct bsc_fd *fd, unsigned int what)
+{
+	char buf[4096];
+	struct sockaddr_in addr;
+	struct mgcp_endpoint *endp;
+	struct mgcp_config *cfg;
+	int rc, proto;
+
+	endp = (struct mgcp_endpoint *) fd->data;
+	cfg = endp->cfg;
+
+	rc = recevice_from(endp, fd->fd, &addr, buf, sizeof(buf));
+	if (rc <= 0)
+		return -1;
+
+	proto = fd == &endp->bts_end.rtp ? PROTO_RTP : PROTO_RTCP;
+
+	/* We have no idea who called us, maybe it is the BTS. */
+	/* it was the BTS... */
+	discover_bts(endp, proto, &addr);
+
+	if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong bts %s on 0x%x\n",
+			inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp));
+		return -1;
+	}
+
+	if (endp->bts_end.rtp_port != addr.sin_port &&
+	    endp->bts_end.rtcp_port != addr.sin_port) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong bts source port %d on 0x%x\n",
+			ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
+		return -1;
+	}
+
+	/* throw away the dummy message */
+	if (rc == 1 && buf[0] == DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n",
+			ENDPOINT_NUMBER(endp));
+		return 0;
+	}
+
+	/* do this before the loop handling */
+	endp->bts_end.packets += 1;
+
+	forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc);
+	if (endp->is_transcoded)
+		return send_transcoder(&endp->trans_bts, endp->cfg, proto == PROTO_RTP, &buf[0], rc);
+	else
+		return send_to(endp, DEST_NETWORK, proto == PROTO_RTP, &addr, &buf[0], rc);
+}
+
+static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp,
+			      int dest, struct bsc_fd *fd)
+{
+	char buf[4096];
+	struct sockaddr_in addr;
+	struct mgcp_config *cfg;
+	int rc, proto;
+
+	cfg = _endp->cfg;
+	rc = recevice_from(_endp, fd->fd, &addr, buf, sizeof(buf));
+	if (rc <= 0)
+		return -1;
+
+	proto = fd == &end->rtp ? PROTO_RTP : PROTO_RTCP;
+
+	if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data not coming from transcoder dest: %d %s on 0x%x\n",
+			dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp));
+		return -1;
+	}
+
+	if (end->rtp_port != addr.sin_port &&
+	    end->rtcp_port != addr.sin_port) {
+		LOGP(DMGCP, LOGL_ERROR,
+			"Data from wrong transcoder dest %d source port %d on 0x%x\n",
+			dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp));
+		return -1;
+	}
+
+	/* throw away the dummy message */
+	if (rc == 1 && buf[0] == DUMMY_LOAD) {
+		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n",
+			dest, ENDPOINT_NUMBER(_endp));
+		return 0;
+	}
+
+	end->packets += 1;
+	return send_to(_endp, dest, proto == PROTO_RTP, &addr, &buf[0], rc);
+}
+
+static int rtp_data_trans_net(struct bsc_fd *fd, unsigned int what)
+{
+	struct mgcp_endpoint *endp;
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	return rtp_data_transcoder(&endp->trans_net, endp, DEST_NETWORK, fd);
+}
+
+static int rtp_data_trans_bts(struct bsc_fd *fd, unsigned int what)
+{
+	struct mgcp_endpoint *endp;
+	endp = (struct mgcp_endpoint *) fd->data;
+
+	return rtp_data_transcoder(&endp->trans_bts, endp, DEST_BTS, fd);
+}
+
+static int create_bind(const char *source_addr, struct bsc_fd *fd, int port)
+{
+	struct sockaddr_in addr;
+	int on = 1;
+
+	fd->fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (fd->fd < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create UDP port.\n");
+		return -1;
+	}
+
+	setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	inet_aton(source_addr, &addr.sin_addr);
+
+	if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		close(fd->fd);
+		fd->fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+static int set_ip_tos(int fd, int tos)
+{
+	int ret;
+	ret = setsockopt(fd, IPPROTO_IP, IP_TOS,
+			 &tos, sizeof(tos));
+	return ret != 0;
+}
+
+static int bind_rtp(struct mgcp_config *cfg, struct mgcp_rtp_end *rtp_end, int endpno)
+{
+	if (create_bind(cfg->source_addr, &rtp_end->rtp, rtp_end->local_port) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n",
+		       cfg->source_addr, rtp_end->local_port, endpno);
+		goto cleanup0;
+	}
+
+	if (create_bind(cfg->source_addr, &rtp_end->rtcp, rtp_end->local_port + 1) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n",
+		       cfg->source_addr, rtp_end->local_port + 1, endpno);
+		goto cleanup1;
+	}
+
+	set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp);
+	set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp);
+
+	rtp_end->rtp.when = BSC_FD_READ;
+	if (bsc_register_fd(&rtp_end->rtp) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n",
+			rtp_end->local_port, endpno);
+		goto cleanup2;
+	}
+
+	rtp_end->rtcp.when = BSC_FD_READ;
+	if (bsc_register_fd(&rtp_end->rtcp) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n",
+			rtp_end->local_port + 1, endpno);
+		goto cleanup3;
+	}
+
+	return 0;
+
+cleanup3:
+	bsc_unregister_fd(&rtp_end->rtp);
+cleanup2:
+	close(rtp_end->rtcp.fd);
+	rtp_end->rtcp.fd = -1;
+cleanup1:
+	close(rtp_end->rtp.fd);
+	rtp_end->rtp.fd = -1;
+cleanup0:
+	return -1;
+}
+
+static int int_bind(const char *port,
+		    struct mgcp_rtp_end *end, int (*cb)(struct bsc_fd *, unsigned),
+		    struct mgcp_endpoint *_endp, int rtp_port)
+{
+	if (end->rtp.fd != -1 || end->rtcp.fd != -1) {
+		LOGP(DMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n",
+			port, ENDPOINT_NUMBER(_endp));
+		mgcp_free_rtp_port(end);
+	}
+
+	end->local_port = rtp_port;
+	end->rtp.cb = cb;
+	end->rtp.data = _endp;
+	end->rtcp.data = _endp;
+	end->rtcp.cb = cb;
+	return bind_rtp(_endp->cfg, end, ENDPOINT_NUMBER(_endp));
+}
+
+
+int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("bts-port", &endp->bts_end,
+			rtp_data_bts, endp, rtp_port);
+}
+
+int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("net-port", &endp->net_end,
+			rtp_data_net, endp, rtp_port);
+}
+
+int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("trans-net", &endp->trans_net,
+			rtp_data_trans_net, endp, rtp_port);
+}
+
+int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port)
+{
+	return int_bind("trans-bts", &endp->trans_bts,
+			rtp_data_trans_bts, endp, rtp_port);
+}
+
+int mgcp_free_rtp_port(struct mgcp_rtp_end *end)
+{
+	if (end->rtp.fd != -1) {
+		close(end->rtp.fd);
+		end->rtp.fd = -1;
+		bsc_unregister_fd(&end->rtp);
+	}
+
+	if (end->rtcp.fd != -1) {
+		close(end->rtcp.fd);
+		end->rtcp.fd = -1;
+		bsc_unregister_fd(&end->rtcp);
+	}
+
+	return 0;
+}
diff --git a/src/libmgcp/mgcp_protocol.c b/src/libmgcp/mgcp_protocol.c
new file mode 100644
index 0000000..ba290dd
--- /dev/null
+++ b/src/libmgcp/mgcp_protocol.c
@@ -0,0 +1,1102 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/select.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+/**
+ * Macro for tokenizing MGCP messages and SDP in one go.
+ *
+ */
+#define MSG_TOKENIZE_START \
+	line_start = 0;						\
+	for (i = 0; i < msgb_l3len(msg); ++i) {			\
+		/* we have a line end */			\
+		if (msg->l3h[i] == '\n') {			\
+			/* skip the first line */		\
+			if (line_start == 0) {			\
+				line_start = i + 1;		\
+				continue;			\
+			}					\
+								\
+			/* check if we have a proper param */	\
+			if (i - line_start == 1 && msg->l3h[line_start] == '\r') { \
+			} else if (i - line_start > 2		\
+			    && islower(msg->l3h[line_start])	\
+			    && msg->l3h[line_start + 1] == '=') { \
+			} else if (i - line_start < 3		\
+			    || msg->l3h[line_start + 1] != ':'	\
+			    || msg->l3h[line_start + 2] != ' ')	\
+				goto error;			\
+								\
+			msg->l3h[i] = '\0';			\
+			if (msg->l3h[i-1] == '\r')		\
+				msg->l3h[i-1] = '\0';
+
+#define MSG_TOKENIZE_END \
+			line_start = i + 1; \
+		}			    \
+	}
+
+static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end);
+
+struct mgcp_request {
+	char *name;
+	struct msgb *(*handle_request) (struct mgcp_config *cfg, struct msgb *msg);
+	char *debug_name;
+};
+
+#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \
+	{ .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME },
+
+static struct msgb *handle_audit_endpoint(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_rsip(struct mgcp_config *cfg, struct msgb *msg);
+static struct msgb *handle_noti_req(struct mgcp_config *cfg, struct msgb *msg);
+
+static void create_transcoder(struct mgcp_endpoint *endp);
+static void delete_transcoder(struct mgcp_endpoint *endp);
+
+static uint32_t generate_call_id(struct mgcp_config *cfg)
+{
+	int i;
+
+	/* use the call id */
+	++cfg->last_call_id;
+
+	/* handle wrap around */
+	if (cfg->last_call_id == CI_UNUSED)
+		++cfg->last_call_id;
+
+	/* callstack can only be of size number_of_endpoints */
+	/* verify that the call id is free, e.g. in case of overrun */
+	for (i = 1; i < cfg->trunk.number_endpoints; ++i)
+		if (cfg->trunk.endpoints[i].ci == cfg->last_call_id)
+			return generate_call_id(cfg);
+
+	return cfg->last_call_id;
+}
+
+/*
+ * array of function pointers for handling various
+ * messages. In the future this might be binary sorted
+ * for performance reasons.
+ */
+static const struct mgcp_request mgcp_requests [] = {
+	MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint")
+	MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection")
+	MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection")
+	MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection")
+	MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest")
+
+	/* SPEC extension */
+	MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress")
+};
+
+static struct msgb *mgcp_msgb_alloc(void)
+{
+	struct msgb *msg;
+	msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+	if (!msg)
+	    LOGP(DMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n");
+
+	return msg;
+}
+
+struct msgb *mgcp_create_response_with_data(int code, const char *txt,
+					    const char *msg, const char *trans,
+					    const char *data)
+{
+	int len;
+	struct msgb *res;
+
+	res = mgcp_msgb_alloc();
+	if (!res)
+		return NULL;
+
+	if (data) {
+		len = snprintf((char *) res->data, 2048, "%d %s%s\r\n%s", code, trans, txt, data);
+	} else {
+		len = snprintf((char *) res->data, 2048, "%d %s%s\r\n", code, trans, txt);
+	}
+
+	res->l2h = msgb_put(res, len);
+	LOGP(DMGCP, LOGL_DEBUG, "Sending response: code: %d for '%s'\n", code, res->l2h);
+	return res;
+}
+
+static struct msgb *create_ok_response(int code, const char *msg, const char *trans)
+{
+	return mgcp_create_response_with_data(code, " OK", msg, trans, NULL);
+}
+
+static struct msgb *create_err_response(int code, const char *msg, const char *trans)
+{
+	return mgcp_create_response_with_data(code, " FAIL", msg, trans, NULL);
+}
+
+static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp,
+					     const char *msg, const char *trans_id)
+{
+	const char *addr = endp->cfg->local_ip;
+	char sdp_record[4096];
+
+	if (!addr)
+		addr = endp->cfg->source_addr;
+
+	snprintf(sdp_record, sizeof(sdp_record) - 1,
+			"I: %u\n\n"
+			"v=0\r\n"
+			"c=IN IP4 %s\r\n"
+			"m=audio %d RTP/AVP %d\r\n"
+			"a=rtpmap:%d %s\r\n",
+			endp->ci, addr, endp->net_end.local_port,
+			endp->bts_end.payload_type, endp->bts_end.payload_type,
+		        endp->tcfg->audio_name);
+	return mgcp_create_response_with_data(200, " OK", msg, trans_id, sdp_record);
+}
+
+/*
+ * handle incoming messages:
+ *   - this can be a command (four letters, space, transaction id)
+ *   - or a response (three numbers, space, transaction id)
+ */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
+{
+        int code;
+	struct msgb *resp = NULL;
+
+	if (msgb_l2len(msg) < 4) {
+		LOGP(DMGCP, LOGL_ERROR, "mgs too short: %d\n", msg->len);
+		return NULL;
+	}
+
+        /* attempt to treat it as a response */
+        if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) {
+		LOGP(DMGCP, LOGL_DEBUG, "Response: Code: %d\n", code);
+	} else {
+		int i, handled = 0;
+		msg->l3h = &msg->l2h[4];
+		for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i)
+			if (strncmp(mgcp_requests[i].name, (const char *) &msg->l2h[0], 4) == 0) {
+				handled = 1;
+				resp = mgcp_requests[i].handle_request(cfg, msg);
+				break;
+			}
+		if (!handled) {
+			LOGP(DMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]);
+		}
+	}
+
+	return resp;
+}
+
+/* string tokenizer for the poor */
+static int find_msg_pointers(struct msgb *msg, struct mgcp_msg_ptr *ptrs, int ptrs_length)
+{
+	int i, found = 0;
+
+	int whitespace = 1;
+	for (i = 0; i < msgb_l3len(msg) && ptrs_length > 0; ++i) {
+		/* if we have a space we found an end */
+		if (msg->l3h[i]	== ' ' || msg->l3h[i] == '\r' || msg->l3h[i] == '\n') {
+			if (!whitespace) {
+				++found;
+				whitespace = 1;
+				ptrs->length = i - ptrs->start - 1;
+				++ptrs;
+				--ptrs_length;
+			} else {
+			    /* skip any number of whitespace */
+			}
+
+			/* line end... stop */
+			if (msg->l3h[i] == '\r' || msg->l3h[i] == '\n')
+				break;
+		} else if (msg->l3h[i] == '\r' || msg->l3h[i] == '\n') {
+			/* line end, be done */
+			break;
+		} else if (whitespace) {
+			whitespace = 0;
+			ptrs->start = i;
+		}
+	}
+
+	if (ptrs_length == 0)
+		return -1;
+	return found;
+}
+
+/**
+ * We have a null terminated string with the endpoint name here. We only
+ * support two kinds. Simple ones as seen on the BSC level and the ones
+ * seen on the trunk side.
+ */
+static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg,
+					     const char *mgcp)
+{
+	char *rest = NULL;
+	struct mgcp_trunk_config *tcfg;
+	int trunk, endp;
+
+	trunk = strtoul(mgcp + 6, &rest, 10);
+	if (rest == NULL || rest[0] != '/' || trunk < 1) {
+		LOGP(DMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp);
+		return NULL;
+	}
+
+	endp = strtoul(rest + 1, &rest, 10);
+	if (rest == NULL || rest[0] != '@') {
+		LOGP(DMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp);
+		return NULL;
+	}
+
+	/* signalling is on timeslot 1 */
+	if (endp == 1)
+		return NULL;
+
+	tcfg = mgcp_trunk_num(cfg, trunk);
+	if (!tcfg) {
+		LOGP(DMGCP, LOGL_ERROR, "The trunk %d is not declared.\n", trunk);
+		return NULL;
+	}
+
+	if (!tcfg->endpoints) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoints of trunk %d not allocated.\n", trunk);
+		return NULL;
+	}
+
+	if (endp < 1 || endp >= tcfg->number_endpoints) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n", mgcp);
+		return NULL;
+	}
+
+	return &tcfg->endpoints[endp];
+}
+
+static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, const char *mgcp)
+{
+	char *endptr = NULL;
+	unsigned int gw = INT_MAX;
+
+	if (strncmp(mgcp, "ds/e1", 5) == 0) {
+		return find_e1_endpoint(cfg, mgcp);
+	} else {
+		gw = strtoul(mgcp, &endptr, 16);
+		if (gw > 0 && gw < cfg->trunk.number_endpoints && strcmp(endptr, "@mgw") == 0)
+			return &cfg->trunk.endpoints[gw];
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Not able to find endpoint: '%s'\n", mgcp);
+	return NULL;
+}
+
+int mgcp_analyze_header(struct mgcp_config *cfg, struct msgb *msg,
+			struct mgcp_msg_ptr *ptr, int size,
+			const char **transaction_id, struct mgcp_endpoint **endp)
+{
+	int found;
+
+	*transaction_id = "000000";
+
+	if (size < 3) {
+		LOGP(DMGCP, LOGL_ERROR, "Not enough space in ptr\n");
+		return -1;
+	}
+
+	found = find_msg_pointers(msg, ptr, size);
+
+	if (found <= 3) {
+		LOGP(DMGCP, LOGL_ERROR, "Gateway: Not enough params. Found: %d\n", found);
+		return -1;
+	}
+
+	/*
+	 * replace the space with \0. the main method gurantess that
+	 * we still have + 1 for null termination
+	 */
+	msg->l3h[ptr[3].start + ptr[3].length + 1] = '\0';
+	msg->l3h[ptr[2].start + ptr[2].length + 1] = '\0';
+	msg->l3h[ptr[1].start + ptr[1].length + 1] = '\0';
+	msg->l3h[ptr[0].start + ptr[0].length + 1] = '\0';
+
+	if (strncmp("1.0", (const char *)&msg->l3h[ptr[3].start], 3) != 0
+	    || strncmp("MGCP", (const char *)&msg->l3h[ptr[2].start], 4) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Wrong MGCP version. Not handling: '%s' '%s'\n",
+			(const char *)&msg->l3h[ptr[3].start],
+			(const char *)&msg->l3h[ptr[2].start]);
+		return -1;
+	}
+
+	*transaction_id = (const char *)&msg->l3h[ptr[0].start];
+	if (endp) {
+		*endp = find_endpoint(cfg, (const char *)&msg->l3h[ptr[1].start]);
+		return *endp == NULL;
+	}
+	return 0;
+}
+
+static int verify_call_id(const struct mgcp_endpoint *endp,
+			  const char *callid)
+{
+	if (strcmp(endp->callid, callid) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n",
+			ENDPOINT_NUMBER(endp), endp->callid, callid);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int verify_ci(const struct mgcp_endpoint *endp,
+		     const char *_ci)
+{
+	uint32_t ci = strtoul(_ci, NULL, 10);
+
+	if (ci != endp->ci) {
+		LOGP(DMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n",
+			ENDPOINT_NUMBER(endp), endp->ci, _ci);
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct msgb *handle_audit_endpoint(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_err_response(500, "AUEP", trans_id);
+	else
+		return create_ok_response(200, "AUEP", trans_id);
+}
+
+static int parse_conn_mode(const char *msg, int *conn_mode)
+{
+	int ret = 0;
+	if (strcmp(msg, "recvonly") == 0)
+		*conn_mode = MGCP_CONN_RECV_ONLY;
+	else if (strcmp(msg, "sendrecv") == 0)
+		*conn_mode = MGCP_CONN_RECV_SEND;
+	else if (strcmp(msg, "sendonly") == 0)
+		*conn_mode = MGCP_CONN_SEND_ONLY;
+	else if (strcmp(msg, "loopback") == 0)
+		*conn_mode = MGCP_CONN_LOOPBACK;
+	else {
+		LOGP(DMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg);
+		ret = -1;
+	}
+
+	return ret;
+}
+
+static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end,
+			 struct mgcp_port_range *range,
+			 int (*alloc)(struct mgcp_endpoint *endp, int port))
+{
+	int i;
+
+	if (range->mode == PORT_ALLOC_STATIC) {
+		end->local_alloc = PORT_ALLOC_STATIC;
+		return 0;
+	}
+
+	/* attempt to find a port */
+	for (i = 0; i < 200; ++i) {
+		int rc;
+
+		if (range->last_port >= range->range_end)
+			range->last_port = range->range_start;
+
+		rc = alloc(endp, range->last_port);
+
+		range->last_port += 2;
+		if (rc == 0) {
+			end->local_alloc = PORT_ALLOC_DYNAMIC;
+			return 0;
+		}
+
+	}
+
+	LOGP(DMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n",
+	     ENDPOINT_NUMBER(endp));
+	return -1;
+}
+
+static int allocate_ports(struct mgcp_endpoint *endp)
+{
+	if (allocate_port(endp, &endp->net_end, &endp->cfg->net_ports,
+			  mgcp_bind_net_rtp_port) != 0)
+		return -1;
+
+	if (allocate_port(endp, &endp->bts_end, &endp->cfg->bts_ports,
+			  mgcp_bind_bts_rtp_port) != 0) {
+		mgcp_rtp_end_reset(&endp->net_end);
+		return -1;
+	}
+
+	if (endp->cfg->transcoder_ip && endp->tcfg->trunk_type == MGCP_TRUNK_VIRTUAL) {
+		if (allocate_port(endp, &endp->trans_net,
+				  &endp->cfg->transcoder_ports,
+				  mgcp_bind_trans_net_rtp_port) != 0) {
+			mgcp_rtp_end_reset(&endp->net_end);
+			mgcp_rtp_end_reset(&endp->bts_end);
+			return -1;
+		}
+
+		if (allocate_port(endp, &endp->trans_bts,
+				  &endp->cfg->transcoder_ports,
+				  mgcp_bind_trans_bts_rtp_port) != 0) {
+			mgcp_rtp_end_reset(&endp->net_end);
+			mgcp_rtp_end_reset(&endp->bts_end);
+			mgcp_rtp_end_reset(&endp->trans_net);
+			return -1;
+		}
+
+		/* remember that we have set up transcoding */
+		endp->is_transcoded = 1;
+	}
+
+	return 0;
+}
+
+static struct msgb *handle_create_con(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, i, line_start;
+	const char *trans_id;
+	struct mgcp_trunk_config *tcfg;
+	struct mgcp_endpoint *endp;
+	int error_code = 400;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_err_response(510, "CRCX", trans_id);
+
+	tcfg = endp->tcfg;
+
+	if (endp->allocated) {
+		if (tcfg->force_realloc) {
+			LOGP(DMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n",
+			    ENDPOINT_NUMBER(endp));
+			mgcp_free_endp(endp);
+			if (cfg->realloc_cb)
+				cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp));
+		} else {
+			LOGP(DMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			return create_err_response(400, "CRCX", trans_id);
+		}
+	}
+
+	/* parse CallID C: and LocalParameters L: */
+	MSG_TOKENIZE_START
+	switch (msg->l3h[line_start]) {
+	case 'L':
+		endp->local_options = talloc_strdup(tcfg->endpoints,
+			(const char *)&msg->l3h[line_start + 3]);
+		break;
+	case 'C':
+		endp->callid = talloc_strdup(tcfg->endpoints,
+			(const char *)&msg->l3h[line_start + 3]);
+		break;
+	case 'M':
+		if (parse_conn_mode((const char *)&msg->l3h[line_start + 3],
+			    &endp->conn_mode) != 0) {
+		    error_code = 517;
+		    goto error2;
+		}
+
+		endp->orig_mode = endp->conn_mode;
+		break;
+	default:
+		LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+			msg->l3h[line_start], msg->l3h[line_start],
+			ENDPOINT_NUMBER(endp));
+		break;
+	}
+	MSG_TOKENIZE_END
+
+	/* initialize */
+	endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0;
+
+	/* set to zero until we get the info */
+	memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr));
+
+	/* bind to the port now */
+	if (allocate_ports(endp) != 0)
+		goto error2;
+
+	/* assign a local call identifier or fail */
+	endp->ci = generate_call_id(cfg);
+	if (endp->ci == CI_UNUSED)
+		goto error2;
+
+	endp->allocated = 1;
+	endp->bts_end.payload_type = tcfg->audio_payload;
+
+	/* policy CB */
+	if (cfg->policy_cb) {
+		switch (cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX, trans_id)) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			mgcp_free_endp(endp);
+			return create_err_response(400, "CRCX", trans_id);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			create_transcoder(endp);
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	LOGP(DMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n",
+		ENDPOINT_NUMBER(endp), endp->ci,
+		endp->net_end.local_port, endp->bts_end.local_port);
+	if (cfg->change_cb)
+		cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX);
+
+	create_transcoder(endp);
+	return create_response_with_sdp(endp, "CRCX", trans_id);
+error:
+	LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n",
+		    hexdump(msg->l3h, msgb_l3len(msg)),
+		    ENDPOINT_NUMBER(endp), line_start, i);
+	return create_err_response(error_code, "CRCX", trans_id);
+
+error2:
+	mgcp_free_endp(endp);
+	LOGP(DMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp));
+	return create_err_response(error_code, "CRCX", trans_id);
+}
+
+static struct msgb *handle_modify_con(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, i, line_start;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+	int error_code = 500;
+	int silent = 0;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_err_response(510, "MDCX", trans_id);
+
+	if (endp->ci == CI_UNUSED) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp));
+		return create_err_response(400, "MDCX", trans_id);
+	}
+
+	MSG_TOKENIZE_START
+	switch (msg->l3h[line_start]) {
+	case 'C': {
+		if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	}
+	case 'I': {
+		if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	}
+	case 'L':
+		/* skip */
+		break;
+	case 'M':
+		if (parse_conn_mode((const char *)&msg->l3h[line_start + 3],
+			    &endp->conn_mode) != 0) {
+		    error_code = 517;
+		    goto error3;
+		}
+		endp->orig_mode = endp->conn_mode;
+		break;
+	case 'Z':
+		silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0;
+		break;
+	case '\0':
+		/* SDP file begins */
+		break;
+	case 'a':
+	case 'o':
+	case 's':
+	case 't':
+	case 'v':
+		/* skip these SDP attributes */
+		break;
+	case 'm': {
+		int port;
+		int payload;
+		const char *param = (const char *)&msg->l3h[line_start];
+
+		if (sscanf(param, "m=audio %d RTP/AVP %d", &port, &payload) == 2) {
+			endp->net_end.rtp_port = htons(port);
+			endp->net_end.rtcp_port = htons(port + 1);
+			endp->net_end.payload_type = payload;
+		}
+		break;
+	}
+	case 'c': {
+		char ipv4[16];
+		const char *param = (const char *)&msg->l3h[line_start];
+
+		if (sscanf(param, "c=IN IP4 %15s", ipv4) == 1) {
+			inet_aton(ipv4, &endp->net_end.addr);
+		}
+		break;
+	}
+	default:
+		LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+			msg->l3h[line_start], msg->l3h[line_start],
+			ENDPOINT_NUMBER(endp));
+		break;
+	}
+	MSG_TOKENIZE_END
+
+	/* policy CB */
+	if (cfg->policy_cb) {
+		switch (cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX, trans_id)) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			if (silent)
+				goto out_silent;
+			return create_err_response(400, "MDCX", trans_id);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	/* modify */
+	LOGP(DMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n",
+		ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+	if (cfg->change_cb)
+		cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX);
+	if (silent)
+		goto out_silent;
+
+	return create_response_with_sdp(endp, "MDCX", trans_id);
+
+error:
+	LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d %d\n",
+		    hexdump(msg->l3h, msgb_l3len(msg)),
+		    ENDPOINT_NUMBER(endp), line_start, i, msg->l3h[line_start]);
+	return create_err_response(error_code, "MDCX", trans_id);
+
+error3:
+	return create_err_response(error_code, "MDCX", trans_id);
+
+
+out_silent:
+	return NULL;
+}
+
+static struct msgb *handle_delete_con(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	int found, i, line_start;
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+	int error_code = 400;
+	int silent = 0;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_err_response(error_code, "DLCX", trans_id);
+
+	if (!endp->allocated) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp));
+		return create_err_response(400, "DLCX", trans_id);
+	}
+
+	MSG_TOKENIZE_START
+	switch (msg->l3h[line_start]) {
+	case 'C': {
+		if (verify_call_id(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	}
+	case 'I': {
+		if (verify_ci(endp, (const char *)&msg->l3h[line_start + 3]) != 0)
+			goto error3;
+		break;
+	case 'Z':
+		silent = strcmp("noanswer", (const char *)&msg->l3h[line_start + 3]) == 0;
+		break;
+	}
+	default:
+		LOGP(DMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n",
+			msg->l3h[line_start], msg->l3h[line_start],
+			ENDPOINT_NUMBER(endp));
+		break;
+	}
+	MSG_TOKENIZE_END
+
+	/* policy CB */
+	if (cfg->policy_cb) {
+		switch (cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX, trans_id)) {
+		case MGCP_POLICY_REJECT:
+			LOGP(DMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n",
+			     ENDPOINT_NUMBER(endp));
+			if (silent)
+				goto out_silent;
+			return create_err_response(400, "DLCX", trans_id);
+			break;
+		case MGCP_POLICY_DEFER:
+			/* stop processing */
+			delete_transcoder(endp);
+			return NULL;
+			break;
+		case MGCP_POLICY_CONT:
+			/* just continue */
+			break;
+		}
+	}
+
+	/* free the connection */
+	LOGP(DMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n",
+		ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port));
+
+	delete_transcoder(endp);
+	mgcp_free_endp(endp);
+	if (cfg->change_cb)
+		cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX);
+
+	if (silent)
+		goto out_silent;
+	return create_ok_response(250, "DLCX", trans_id);
+
+error:
+	LOGP(DMGCP, LOGL_ERROR, "Malformed line: %s on 0x%x with: line_start: %d %d\n",
+		    hexdump(msg->l3h, msgb_l3len(msg)),
+		    ENDPOINT_NUMBER(endp), line_start, i);
+	return create_err_response(error_code, "DLCX", trans_id);
+
+error3:
+	return create_err_response(error_code, "DLCX", trans_id);
+
+out_silent:
+	return NULL;
+}
+
+static struct msgb *handle_rsip(struct mgcp_config *cfg, struct msgb *msg)
+{
+	if (cfg->reset_cb)
+		cfg->reset_cb(cfg);
+	return NULL;
+}
+
+/*
+ * This can request like DTMF detection and forward, fax detection... it
+ * can also request when the notification should be send and such. We don't
+ * do this right now.
+ */
+static struct msgb *handle_noti_req(struct mgcp_config *cfg, struct msgb *msg)
+{
+	struct mgcp_msg_ptr data_ptrs[6];
+	const char *trans_id;
+	struct mgcp_endpoint *endp;
+	int found;
+
+	found = mgcp_analyze_header(cfg, msg, data_ptrs, ARRAY_SIZE(data_ptrs), &trans_id, &endp);
+	if (found != 0)
+		return create_err_response(400, "RQNT", trans_id);
+
+	if (!endp->allocated) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", ENDPOINT_NUMBER(endp));
+		return create_err_response(400, "RQNT", trans_id);
+	}
+	return create_ok_response(200, "RQNT", trans_id);
+}
+
+struct mgcp_config *mgcp_config_alloc(void)
+{
+	struct mgcp_config *cfg;
+
+	cfg = talloc_zero(NULL, struct mgcp_config);
+	if (!cfg) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to allocate config.\n");
+		return NULL;
+	}
+
+	cfg->source_port = 2427;
+	cfg->source_addr = talloc_strdup(cfg, "0.0.0.0");
+
+	cfg->transcoder_remote_base = 4000;
+
+	cfg->bts_ports.base_port = RTP_PORT_DEFAULT;
+	cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT;
+
+	/* default trunk handling */
+	cfg->trunk.cfg = cfg;
+	cfg->trunk.trunk_nr = 0;
+	cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL;
+	cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000");
+	cfg->trunk.audio_payload = 126;
+
+	INIT_LLIST_HEAD(&cfg->trunks);
+
+	return cfg;
+}
+
+struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr)
+{
+	struct mgcp_trunk_config *trunk;
+
+	trunk = talloc_zero(cfg, struct mgcp_trunk_config);
+	if (!trunk) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate.\n");
+		return NULL;
+	}
+
+	trunk->cfg = cfg;
+	trunk->trunk_type = MGCP_TRUNK_E1;
+	trunk->trunk_nr = nr;
+	trunk->audio_name = talloc_strdup(cfg, "AMR/8000");
+	trunk->audio_payload = 126;
+	trunk->number_endpoints = 33;
+	llist_add_tail(&trunk->entry, &cfg->trunks);
+	return trunk;
+}
+
+struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index)
+{
+	struct mgcp_trunk_config *trunk;
+
+	llist_for_each_entry(trunk, &cfg->trunks, entry)
+		if (trunk->trunk_nr == index)
+			return trunk;
+
+	return NULL;
+}
+
+static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end)
+{
+	if (end->local_alloc == PORT_ALLOC_DYNAMIC) {
+		mgcp_free_rtp_port(end);
+		end->local_port = 0;
+	}
+
+	end->packets = 0;
+	memset(&end->addr, 0, sizeof(end->addr));
+	end->rtp_port = end->rtcp_port = 0;
+	end->payload_type = -1;
+	end->local_alloc = -1;
+}
+
+static void mgcp_rtp_end_init(struct mgcp_rtp_end *end)
+{
+	mgcp_rtp_end_reset(end);
+	end->rtp.fd = -1;
+	end->rtcp.fd = -1;
+}
+
+int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg)
+{
+	int i;
+
+	/* Initialize all endpoints */
+	tcfg->endpoints = _talloc_zero_array(tcfg->cfg,
+				       sizeof(struct mgcp_endpoint),
+				       tcfg->number_endpoints, "endpoints");
+	if (!tcfg->endpoints)
+		return -1;
+
+	for (i = 0; i < tcfg->number_endpoints; ++i) {
+		tcfg->endpoints[i].ci = CI_UNUSED;
+		tcfg->endpoints[i].cfg = tcfg->cfg;
+		tcfg->endpoints[i].tcfg = tcfg;
+		mgcp_rtp_end_init(&tcfg->endpoints[i].net_end);
+		mgcp_rtp_end_init(&tcfg->endpoints[i].bts_end);
+		mgcp_rtp_end_init(&tcfg->endpoints[i].trans_net);
+		mgcp_rtp_end_init(&tcfg->endpoints[i].trans_bts);
+	}
+
+	return 0;
+}
+
+void mgcp_free_endp(struct mgcp_endpoint *endp)
+{
+	LOGP(DMGCP, LOGL_DEBUG, "Deleting endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp));
+	endp->ci = CI_UNUSED;
+	endp->allocated = 0;
+
+	if (endp->callid) {
+		talloc_free(endp->callid);
+		endp->callid = NULL;
+	}
+
+	if (endp->local_options) {
+		talloc_free(endp->local_options);
+		endp->local_options = NULL;
+	}
+
+	mgcp_rtp_end_reset(&endp->bts_end);
+	mgcp_rtp_end_reset(&endp->net_end);
+	mgcp_rtp_end_reset(&endp->trans_net);
+	mgcp_rtp_end_reset(&endp->trans_bts);
+	endp->is_transcoded = 0;
+
+	memset(&endp->net_state, 0, sizeof(endp->net_state));
+	memset(&endp->bts_state, 0, sizeof(endp->bts_state));
+
+	endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE;
+	endp->allow_patch = 0;
+
+	memset(&endp->taps, 0, sizeof(endp->taps));
+}
+
+static int send_trans(struct mgcp_config *cfg, const char *buf, int len)
+{
+	struct sockaddr_in addr;
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr = cfg->transcoder_in;
+	addr.sin_port = htons(2427);
+	return sendto(cfg->gw_fd.bfd.fd, buf, len, 0,
+		      (struct sockaddr *) &addr, sizeof(addr));
+}
+
+static void send_msg(struct mgcp_endpoint *endp, int endpoint, int port,
+		     const char *msg, const char *mode)
+{
+	char buf[2096];
+	int len;
+
+	/* hardcoded to AMR right now, we do not know the real type at this point */
+	len = snprintf(buf, sizeof(buf),
+			"%s 42 %x@mgw MGCP 1.0\r\n"
+			"C: 4256\r\n"
+			"M: %s\r\n"
+			"\r\n"
+			"c=IN IP4 %s\r\n"
+			"m=audio %d RTP/AVP %d\r\n"
+			"a=rtpmap:%d %s\r\n",
+			msg, endpoint, mode, endp->cfg->source_addr,
+			port, endp->tcfg->audio_payload,
+			endp->tcfg->audio_payload, endp->tcfg->audio_name);
+
+	if (len < 0)
+		return;
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	send_trans(endp->cfg, buf, len);
+}
+
+static void send_dlcx(struct mgcp_endpoint *endp, int endpoint)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+			"DLCX 43 %x@mgw MGCP 1.0\r\n"
+			"C: 4256\r\n"
+			, endpoint);
+
+	if (len < 0)
+		return;
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	send_trans(endp->cfg, buf, len);
+}
+
+static void create_transcoder(struct mgcp_endpoint *endp)
+{
+	int port;
+	int in_endp = ENDPOINT_NUMBER(endp);
+	int out_endp = endp_back_channel(in_endp);
+
+	if (!endp->is_transcoded)
+		return;
+
+	send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv");
+	send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv");
+	send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv");
+	send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv");
+
+	port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base);
+	endp->trans_bts.rtp_port = htons(port);
+	endp->trans_bts.rtcp_port = htons(port + 1);
+
+	port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base);
+	endp->trans_net.rtp_port = htons(port);
+	endp->trans_net.rtcp_port = htons(port + 1);
+}
+
+static void delete_transcoder(struct mgcp_endpoint *endp)
+{
+	int in_endp = ENDPOINT_NUMBER(endp);
+	int out_endp = endp_back_channel(in_endp);
+
+	if (!endp->is_transcoded)
+		return;
+
+	send_dlcx(endp, in_endp);
+	send_dlcx(endp, out_endp);
+}
+
+int mgcp_reset_transcoder(struct mgcp_config *cfg)
+{
+	if (!cfg->transcoder_ip)
+		return 0;
+
+	static const char mgcp_reset[] = {
+	    "RSIP 1 13@mgw MGCP 1.0\r\n"
+	};
+
+	return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1);
+}
diff --git a/src/libmgcp/mgcp_vty.c b/src/libmgcp/mgcp_vty.c
new file mode 100644
index 0000000..c299a98
--- /dev/null
+++ b/src/libmgcp/mgcp_vty.c
@@ -0,0 +1,742 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The protocol implementation */
+
+/*
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/types.h>
+
+#include <osmocore/talloc.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+
+#include <string.h>
+
+static struct mgcp_config *g_cfg = NULL;
+
+static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr)
+{
+	struct mgcp_trunk_config *trunk;
+
+	if (nr == 0)
+		trunk = &cfg->trunk;
+	else
+		trunk = mgcp_trunk_num(cfg, nr);
+
+	return trunk;
+}
+
+/*
+ * vty code for mgcp below
+ */
+struct cmd_node mgcp_node = {
+	MGCP_NODE,
+	"%s(mgcp)#",
+	1,
+};
+
+struct cmd_node trunk_node = {
+	TRUNK_NODE,
+	"%s(trunk)#",
+	1,
+};
+
+static int config_write_mgcp(struct vty *vty)
+{
+	vty_out(vty, "mgcp%s", VTY_NEWLINE);
+	if (g_cfg->local_ip)
+		vty_out(vty, "  local ip %s%s", g_cfg->local_ip, VTY_NEWLINE);
+	if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0)
+		vty_out(vty, "  bts ip %s%s", g_cfg->bts_ip, VTY_NEWLINE);
+	vty_out(vty, "  bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE);
+	vty_out(vty, "  bind port %u%s", g_cfg->source_port, VTY_NEWLINE);
+
+	if (g_cfg->bts_ports.mode == PORT_ALLOC_STATIC)
+		vty_out(vty, "  rtp bts-base %u%s", g_cfg->bts_ports.base_port, VTY_NEWLINE);
+	else
+		vty_out(vty, "  rtp bts-range %u %u%s",
+			g_cfg->bts_ports.range_start, g_cfg->bts_ports.range_end, VTY_NEWLINE);
+
+	if (g_cfg->net_ports.mode == PORT_ALLOC_STATIC)
+		vty_out(vty, "  rtp net-base %u%s", g_cfg->net_ports.base_port, VTY_NEWLINE);
+	else
+		vty_out(vty, "  rtp net-range %u %u%s",
+			g_cfg->net_ports.range_start, g_cfg->net_ports.range_end, VTY_NEWLINE);
+
+	vty_out(vty, "  rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE);
+	if (g_cfg->trunk.audio_payload != -1)
+		vty_out(vty, "  sdp audio payload number %d%s",
+			g_cfg->trunk.audio_payload, VTY_NEWLINE);
+	if (g_cfg->trunk.audio_name)
+		vty_out(vty, "  sdp audio payload name %s%s",
+			g_cfg->trunk.audio_name, VTY_NEWLINE);
+	vty_out(vty, "  loop %u%s", !!g_cfg->trunk.audio_loop, VTY_NEWLINE);
+	vty_out(vty, "  number endpoints %u%s", g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE);
+	if (g_cfg->call_agent_addr)
+		vty_out(vty, "  call agent ip %s%s", g_cfg->call_agent_addr, VTY_NEWLINE);
+	if (g_cfg->transcoder_ip)
+		vty_out(vty, "  transcoder-mgw %s%s", g_cfg->transcoder_ip, VTY_NEWLINE);
+
+	if (g_cfg->transcoder_ports.mode == PORT_ALLOC_STATIC)
+		vty_out(vty, "  rtp transcoder-base %u%s", g_cfg->transcoder_ports.base_port, VTY_NEWLINE);
+	else
+		vty_out(vty, "  rtp transcoder-range %u %u%s",
+			g_cfg->transcoder_ports.range_start, g_cfg->transcoder_ports.range_end, VTY_NEWLINE);
+	vty_out(vty, "  transcoder-remote-base %u%s", g_cfg->transcoder_remote_base, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg)
+{
+	int i;
+
+	vty_out(vty, "%s trunk nr %d with %d endpoints:%s",
+		cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1",
+		cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE);
+
+	if (!cfg->endpoints) {
+		vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE);
+		return;
+	}
+
+	for (i = 1; i < cfg->number_endpoints; ++i) {
+		struct mgcp_endpoint *endp = &cfg->endpoints[i];
+		vty_out(vty,
+			" Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s "
+			"traffic received bts: %u/%u  remote: %u/%u transcoder: %u/%u%s",
+			i, endp->ci,
+			ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port),
+			ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port),
+			inet_ntoa(endp->bts_end.addr),
+			endp->bts_end.packets, endp->bts_state.lost_no,
+			endp->net_end.packets, endp->net_state.lost_no,
+			endp->trans_net.packets, endp->trans_bts.packets,
+			VTY_NEWLINE);
+	}
+}
+
+DEFUN(show_mcgp, show_mgcp_cmd, "show mgcp",
+      SHOW_STR "Display information about the MGCP Media Gateway")
+{
+	struct mgcp_trunk_config *trunk;
+
+	dump_trunk(vty, &g_cfg->trunk);
+
+	llist_for_each_entry(trunk, &g_cfg->trunks, entry)
+		dump_trunk(vty, trunk);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp,
+      cfg_mgcp_cmd,
+      "mgcp",
+      "Configure the MGCP")
+{
+	vty->node = MGCP_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_local_ip,
+      cfg_mgcp_local_ip_cmd,
+      "local ip A.B.C.D",
+      "Set the IP to be used in SDP records")
+{
+	bsc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bts_ip,
+      cfg_mgcp_bts_ip_cmd,
+      "bts ip A.B.C.D",
+      "Set the IP of the BTS for RTP forwarding")
+{
+	bsc_replace_string(g_cfg, &g_cfg->bts_ip, argv[0]);
+	inet_aton(g_cfg->bts_ip, &g_cfg->bts_in);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_ip,
+      cfg_mgcp_bind_ip_cmd,
+      "bind ip A.B.C.D",
+      "Bind the MGCP to this local addr")
+{
+	bsc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_port,
+      cfg_mgcp_bind_port_cmd,
+      "bind port <0-65534>",
+      "Bind the MGCP to this port")
+{
+	unsigned int port = atoi(argv[0]);
+	g_cfg->source_port = port;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_bind_early,
+      cfg_mgcp_bind_early_cmd,
+      "bind early (0|1)",
+      "Bind all RTP ports early")
+{
+	vty_out(vty, "bind early is deprecated, remove it from the config.\n");
+	return CMD_WARNING;
+}
+
+static void parse_base(struct mgcp_port_range *range, const char **argv)
+{
+	unsigned int port = atoi(argv[0]);
+	range->mode = PORT_ALLOC_STATIC;
+	range->base_port = port;
+}
+
+static void parse_range(struct mgcp_port_range *range, const char **argv)
+{
+	range->mode = PORT_ALLOC_DYNAMIC;
+	range->range_start = atoi(argv[0]);
+	range->range_end = atoi(argv[1]);
+	range->last_port = g_cfg->bts_ports.range_start;
+}
+
+
+DEFUN(cfg_mgcp_rtp_bts_base_port,
+      cfg_mgcp_rtp_bts_base_port_cmd,
+      "rtp bts-base <0-65534>",
+      "Base port to use")
+{
+	parse_base(&g_cfg->bts_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_bts_range,
+      cfg_mgcp_rtp_bts_range_cmd,
+      "rtp bts-range <0-65534> <0-65534>",
+      "Range of ports to allocate for endpoints\n"
+      "Start of the range of ports\n" "End of the range of ports\n")
+{
+	parse_range(&g_cfg->bts_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_range,
+      cfg_mgcp_rtp_net_range_cmd,
+      "rtp net-range <0-65534> <0-65534>",
+      "Range of ports to allocate for endpoints\n"
+      "Start of the range of ports\n" "End of the range of ports\n")
+{
+	parse_range(&g_cfg->net_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_net_base_port,
+      cfg_mgcp_rtp_net_base_port_cmd,
+      "rtp net-base <0-65534>",
+      "Base port to use for network port\n" "Port\n")
+{
+	parse_base(&g_cfg->net_ports, argv);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_rtp_bts_base_port, cfg_mgcp_rtp_base_port_cmd,
+      "rtp base <0-65534>", "Base port to use")
+
+DEFUN(cfg_mgcp_rtp_transcoder_range,
+      cfg_mgcp_rtp_transcoder_range_cmd,
+      "rtp transcoder-range <0-65534> <0-65534>",
+      "Range of ports to allocate for the transcoder\n"
+      "Start of the range of ports\n" "End of the range of ports\n")
+{
+	parse_range(&g_cfg->transcoder_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_transcoder_base,
+      cfg_mgcp_rtp_transcoder_base_cmd,
+      "rtp transcoder-base <0-65534>",
+      "Base port for the transcoder range\n" "Port\n")
+{
+	parse_base(&g_cfg->transcoder_ports, argv);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_rtp_ip_dscp,
+      cfg_mgcp_rtp_ip_dscp_cmd,
+      "rtp ip-dscp <0-255>",
+      "Set the IP_TOS socket attribute on the RTP/RTCP sockets.\n" "The DSCP value.")
+{
+	int dscp = atoi(argv[0]);
+	g_cfg->endp_dscp = dscp;
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd,
+      "rtp ip-tos <0-255>",
+      "Set the IP_TOS socket attribute on the RTP/RTCP sockets.\n" "The DSCP value.")
+
+
+DEFUN(cfg_mgcp_sdp_payload_number,
+      cfg_mgcp_sdp_payload_number_cmd,
+      "sdp audio payload number <1-255>",
+      "Set the audio codec to use")
+{
+	unsigned int payload = atoi(argv[0]);
+	g_cfg->trunk.audio_payload = payload;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_sdp_payload_name,
+      cfg_mgcp_sdp_payload_name_cmd,
+      "sdp audio payload name NAME",
+      "Set the audio name to use")
+{
+	bsc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_loop,
+      cfg_mgcp_loop_cmd,
+      "loop (0|1)",
+      "Loop the audio")
+{
+	g_cfg->trunk.audio_loop = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_number_endp,
+      cfg_mgcp_number_endp_cmd,
+      "number endpoints <0-65534>",
+      "The number of endpoints to allocate. This is not dynamic.")
+{
+	/* + 1 as we start counting at one */
+	g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_agent_addr,
+      cfg_mgcp_agent_addr_cmd,
+      "call agent ip IP",
+      "Set the address of the call agent.")
+{
+	bsc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_transcoder,
+      cfg_mgcp_transcoder_cmd,
+      "transcoder-mgw A.B.C.D",
+      "Use a MGW to detranscoder RTP\n"
+      "The IP address of the MGW")
+{
+	bsc_replace_string(g_cfg, &g_cfg->transcoder_ip, argv[0]);
+	inet_aton(g_cfg->transcoder_ip, &g_cfg->transcoder_in);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_no_transcoder,
+      cfg_mgcp_no_transcoder_cmd,
+      NO_STR "transcoder-mgw",
+      "Disable the transcoding\n")
+{
+	if (g_cfg->transcoder_ip) {
+		LOGP(DMGCP, LOGL_NOTICE, "Disabling transcoding on future calls.\n");
+		talloc_free(g_cfg->transcoder_ip);
+		g_cfg->transcoder_ip = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_transcoder_remote_base,
+      cfg_mgcp_transcoder_remote_base_cmd,
+      "transcoder-remote-base <0-65534>",
+      "Set the base port for the transcoder\n" "The RTP base port on the transcoder")
+{
+	g_cfg->transcoder_remote_base = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd,
+      "trunk <1-64>",
+      "Configure a SS7 trunk\n" "Trunk Nr\n")
+{
+	struct mgcp_trunk_config *trunk;
+	int index = atoi(argv[0]);
+
+	trunk = mgcp_trunk_num(g_cfg, index);
+	if (!trunk)
+		trunk = mgcp_trunk_alloc(g_cfg, index);
+
+	if (!trunk) {
+		vty_out(vty, "%%Unable to allocate trunk %u.%s",
+			index, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	vty->node = TRUNK_NODE;
+	vty->index = trunk;
+	return CMD_SUCCESS;
+}
+
+static int config_write_trunk(struct vty *vty)
+{
+	struct mgcp_trunk_config *trunk;
+
+	llist_for_each_entry(trunk, &g_cfg->trunks, entry) {
+		vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE);
+		vty_out(vty, "  sdp audio payload number %d%s",
+			trunk->audio_payload, VTY_NEWLINE);
+		vty_out(vty, "  sdp audio payload name %s%s",
+			trunk->audio_name, VTY_NEWLINE);
+		vty_out(vty, "  loop %d%s",
+			trunk->audio_loop, VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_payload_number,
+      cfg_trunk_payload_number_cmd,
+      "sdp audio payload number <1-255>",
+      "SDP related\n" "Audio\n" "Payload\n" "Payload Number\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+	unsigned int payload = atoi(argv[0]);
+
+	trunk->audio_payload = payload;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_payload_name,
+      cfg_trunk_payload_name_cmd,
+      "sdp audio payload name NAME",
+      "SDP related\n" "Audio\n" "Payload\n" "Payload Name\n")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+
+	bsc_replace_string(g_cfg, &trunk->audio_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trunk_loop,
+      cfg_trunk_loop_cmd,
+      "loop (0|1)",
+      "Loop the audio")
+{
+	struct mgcp_trunk_config *trunk = vty->index;
+
+	trunk->audio_loop = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(loop_endp,
+      loop_endp_cmd,
+      "loop-endpoint <0-64> NAME (0|1)",
+      "Loop a given endpoint\n" "Trunk number\n"
+      "The name in hex of the endpoint\n" "Disable the loop\n" "Enable the loop\n")
+{
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	int endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Loopback number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+
+	endp = &trunk->endpoints[endp_no];
+	int loop = atoi(argv[2]);
+
+	if (loop)
+		endp->conn_mode = MGCP_CONN_LOOPBACK;
+	else
+		endp->conn_mode = endp->orig_mode;
+	endp->allow_patch = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(tap_call,
+      tap_call_cmd,
+      "tap-call <0-64> ENDPOINT (bts-in|bts-out|net-in|net-out) A.B.C.D <0-65534>",
+      "Forward data on endpoint to a different system\n" "Trunk number\n"
+      "The endpoint in hex\n"
+      "Forward the data coming from the bts\n"
+      "Forward the data coming from the bts leaving to the network\n"
+      "Forward the data coming from the net\n"
+      "Forward the data coming from the net leaving to the bts\n"
+      "destination IP of the data\n" "destination port\n")
+{
+	struct mgcp_rtp_tap *tap;
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+	int port = 0;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	int endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	endp = &trunk->endpoints[endp_no];
+
+	if (strcmp(argv[2], "bts-in") == 0) {
+		port = MGCP_TAP_BTS_IN;
+	} else if (strcmp(argv[2], "bts-out") == 0) {
+		port = MGCP_TAP_BTS_OUT;
+	} else if (strcmp(argv[2], "net-in") == 0) {
+		port = MGCP_TAP_NET_IN;
+	} else if (strcmp(argv[2], "net-out") == 0) {
+		port = MGCP_TAP_NET_OUT;
+	} else {
+		vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	tap = &endp->taps[port];
+	memset(&tap->forward, 0, sizeof(tap->forward));
+	inet_aton(argv[3], &tap->forward.sin_addr);
+	tap->forward.sin_port = htons(atoi(argv[4]));
+	tap->enabled = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(free_endp, free_endp_cmd,
+      "free-endpoint <0-64> NUMBER",
+      "Free the given endpoint\n" "Trunk number\n"
+      "Endpoint number in hex.\n")
+{
+	struct mgcp_trunk_config *trunk;
+	struct mgcp_endpoint *endp;
+
+	trunk = find_trunk(g_cfg, atoi(argv[0]));
+	if (!trunk) {
+		vty_out(vty, "%%Trunk %d not found in the config.%s",
+			atoi(argv[0]), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!trunk->endpoints) {
+		vty_out(vty, "%%Trunk %d has no endpoints allocated.%s",
+			trunk->trunk_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	int endp_no = strtoul(argv[1], NULL, 16);
+	if (endp_no < 1 || endp_no >= trunk->number_endpoints) {
+		vty_out(vty, "Endpoint number %s/%d is invalid.%s",
+		argv[1], endp_no, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	endp = &trunk->endpoints[endp_no];
+	mgcp_free_endp(endp);
+	return CMD_SUCCESS;
+}
+
+int mgcp_vty_init(void)
+{
+	install_element_ve(&show_mgcp_cmd);
+	install_element(ENABLE_NODE, &loop_endp_cmd);
+	install_element(ENABLE_NODE, &tap_call_cmd);
+	install_element(ENABLE_NODE, &free_endp_cmd);
+
+	install_element(CONFIG_NODE, &cfg_mgcp_cmd);
+	install_node(&mgcp_node, config_write_mgcp);
+
+	install_default(MGCP_NODE);
+	install_element(MGCP_NODE, &ournode_exit_cmd);
+	install_element(MGCP_NODE, &ournode_end_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bts_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_net_base_port_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_range_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_range_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_transcoder_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_no_transcoder_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_transcoder_remote_base_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_loop_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd);
+
+	install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd);
+	install_node(&trunk_node, config_write_trunk);
+	install_default(TRUNK_NODE);
+	install_element(TRUNK_NODE, &ournode_exit_cmd);
+	install_element(TRUNK_NODE, &ournode_end_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd);
+	install_element(TRUNK_NODE, &cfg_trunk_loop_cmd);
+
+	return 0;
+}
+
+static int allocate_trunk(struct mgcp_trunk_config *trunk)
+{
+	int i;
+	struct mgcp_config *cfg = trunk->cfg;
+
+	if (mgcp_endpoints_allocate(trunk) != 0) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Failed to allocate %d endpoints on trunk %d.\n",
+		     trunk->number_endpoints, trunk->trunk_nr);
+		return -1;
+	}
+
+	/* early bind */
+	for (i = 1; i < trunk->number_endpoints; ++i) {
+		struct mgcp_endpoint *endp = &trunk->endpoints[i];
+
+		if (cfg->bts_ports.mode == PORT_ALLOC_STATIC) {
+			cfg->last_bts_port += 2;
+			if (mgcp_bind_bts_rtp_port(endp, cfg->last_bts_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL,
+				     "Failed to bind: %d\n", cfg->last_bts_port);
+				return -1;
+			}
+			endp->bts_end.local_alloc = PORT_ALLOC_STATIC;
+		}
+
+		if (cfg->net_ports.mode == PORT_ALLOC_STATIC) {
+			cfg->last_net_port += 2;
+			if (mgcp_bind_net_rtp_port(endp, cfg->last_net_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL,
+				     "Failed to bind: %d\n", cfg->last_net_port);
+				return -1;
+			}
+			endp->net_end.local_alloc = PORT_ALLOC_STATIC;
+		}
+
+		if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL &&
+		    cfg->transcoder_ip && cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) {
+			int rtp_port;
+
+			/* network side */
+			rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp),
+						      cfg->transcoder_ports.base_port);
+			if (mgcp_bind_trans_net_rtp_port(endp, rtp_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+				return -1;
+			}
+			endp->trans_net.local_alloc = PORT_ALLOC_STATIC;
+
+			/* bts side */
+			rtp_port = rtp_calculate_port(endp_back_channel(ENDPOINT_NUMBER(endp)),
+						      cfg->transcoder_ports.base_port);
+			if (mgcp_bind_trans_bts_rtp_port(endp, rtp_port) != 0) {
+				LOGP(DMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port);
+				return -1;
+			}
+			endp->trans_bts.local_alloc = PORT_ALLOC_STATIC;
+		}
+	}
+
+	return 0;
+}
+
+int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg)
+{
+	int rc;
+	struct mgcp_trunk_config *trunk;
+
+	g_cfg = cfg;
+	rc = vty_read_config_file(config_file, NULL);
+	if (rc < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+
+	if (!g_cfg->bts_ip)
+		fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n");
+
+	if (!g_cfg->source_addr) {
+		fprintf(stderr, "You need to specify a bind address.\n");
+		return -1;
+	}
+
+	/* initialize the last ports */
+	g_cfg->last_bts_port = rtp_calculate_port(0, g_cfg->bts_ports.base_port);
+	g_cfg->last_net_port = rtp_calculate_port(0, g_cfg->net_ports.base_port);
+
+	if (allocate_trunk(&g_cfg->trunk) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to initialize the virtual trunk.\n");
+		return -1;
+	}
+
+	llist_for_each_entry(trunk, &g_cfg->trunks, entry) {
+		if (allocate_trunk(trunk) != 0) {
+			LOGP(DMGCP, LOGL_ERROR,
+			     "Failed to initialize E1 trunk %d.\n", trunk->trunk_nr);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
new file mode 100644
index 0000000..7d895c3
--- /dev/null
+++ b/src/libmsc/Makefile.am
@@ -0,0 +1,19 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libmsc.a
+
+libmsc_a_SOURCES =	auth.c \
+			db.c \
+			gsm_04_08.c gsm_04_11.c gsm_04_80.c \
+			gsm_subscriber.c \
+			mncc.c mncc_builtin.c mncc_sock.c \
+			rrlp.c \
+			silent_call.c \
+			sms_queue.c \
+			token_auth.c \
+			ussd.c \
+			vty_interface_layer3.c \
+			osmo_msc.c
+
diff --git a/src/libmsc/Makefile.in b/src/libmsc/Makefile.in
new file mode 100644
index 0000000..1ebc032
--- /dev/null
+++ b/src/libmsc/Makefile.in
@@ -0,0 +1,482 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libmsc
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libmsc_a_AR = $(AR) $(ARFLAGS)
+libmsc_a_LIBADD =
+am_libmsc_a_OBJECTS = auth.$(OBJEXT) db.$(OBJEXT) gsm_04_08.$(OBJEXT) \
+	gsm_04_11.$(OBJEXT) gsm_04_80.$(OBJEXT) \
+	gsm_subscriber.$(OBJEXT) mncc.$(OBJEXT) mncc_builtin.$(OBJEXT) \
+	mncc_sock.$(OBJEXT) rrlp.$(OBJEXT) silent_call.$(OBJEXT) \
+	sms_queue.$(OBJEXT) token_auth.$(OBJEXT) ussd.$(OBJEXT) \
+	vty_interface_layer3.$(OBJEXT) osmo_msc.$(OBJEXT)
+libmsc_a_OBJECTS = $(am_libmsc_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libmsc_a_SOURCES)
+DIST_SOURCES = $(libmsc_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libmsc.a
+libmsc_a_SOURCES = auth.c \
+			db.c \
+			gsm_04_08.c gsm_04_11.c gsm_04_80.c \
+			gsm_subscriber.c \
+			mncc.c mncc_builtin.c mncc_sock.c \
+			rrlp.c \
+			silent_call.c \
+			sms_queue.c \
+			token_auth.c \
+			ussd.c \
+			vty_interface_layer3.c \
+			osmo_msc.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libmsc/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libmsc/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libmsc.a: $(libmsc_a_OBJECTS) $(libmsc_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libmsc.a
+	$(AM_V_AR)$(libmsc_a_AR) libmsc.a $(libmsc_a_OBJECTS) $(libmsc_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libmsc.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/auth.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_08.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_11.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_80.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_subscriber.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mncc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mncc_builtin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mncc_sock.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_msc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrlp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/silent_call.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sms_queue.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/token_auth.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ussd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vty_interface_layer3.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libmsc/auth.c b/src/libmsc/auth.c
new file mode 100644
index 0000000..e09bde5
--- /dev/null
+++ b/src/libmsc/auth.c
@@ -0,0 +1,132 @@
+/* Authentication related functions */
+
+/*
+ * (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/auth.h>
+#include <openbsc/gsm_data.h>
+
+#include <osmocore/comp128.h>
+
+#include <stdlib.h>
+
+
+static int
+_use_xor(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+	int i, l = ainfo->a3a8_ki_len;
+
+	if ((l > A38_XOR_MAX_KEY_LEN) || (l < A38_XOR_MIN_KEY_LEN)) {
+		LOGP(DMM, LOGL_ERROR, "Invalid XOR key (len=%d) %s\n",
+			ainfo->a3a8_ki_len,
+			hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+		return -1;
+	}
+
+	for (i=0; i<4; i++)
+		atuple->sres[i] = atuple->rand[i] ^ ainfo->a3a8_ki[i];
+	for (i=4; i<12; i++)
+		atuple->kc[i-4] = atuple->rand[i] ^ ainfo->a3a8_ki[i];
+
+	return 0;
+}
+
+static int
+_use_comp128_v1(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+	if (ainfo->a3a8_ki_len != A38_COMP128_KEY_LEN) {
+		LOGP(DMM, LOGL_ERROR, "Invalid COMP128v1 key (len=%d) %s\n",
+			ainfo->a3a8_ki_len,
+			hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+		return -1;
+	}
+
+	comp128(ainfo->a3a8_ki, atuple->rand, atuple->sres, atuple->kc);
+
+	return 0;
+}
+
+/* Return values 
+ *  -1 -> Internal error
+ *   0 -> Not available
+ *   1 -> Tuple returned, need to do auth, then enable cipher
+ *   2 -> Tuple returned, need to enable cipher
+ */
+int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple,
+                              struct gsm_subscriber *subscr, int key_seq)
+{
+	struct gsm_auth_info ainfo;
+	int i, rc;
+
+	/* Get subscriber info (if any) */
+	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+	if (rc < 0) {
+		LOGP(DMM, LOGL_NOTICE,
+			"No retrievable Ki for subscriber, skipping auth\n");
+		return rc == -ENOENT ? AUTH_NOT_AVAIL : -1;
+	}
+
+	/* If possible, re-use the last tuple and skip auth */
+	rc = db_get_lastauthtuple_for_subscr(atuple, subscr);
+	if ((rc == 0) &&
+	    (key_seq != GSM_KEY_SEQ_INVAL) &&
+	    (atuple->use_count < 3))
+	{
+		atuple->use_count++;
+		db_sync_lastauthtuple_for_subscr(atuple, subscr);
+		DEBUGP(DMM, "Auth tuple use < 3, just doing ciphering\n");
+		return AUTH_DO_CIPH;
+	}
+
+	/* Generate a new one */
+	atuple->use_count = 1;
+	atuple->key_seq = (atuple->key_seq + 1) % 7;
+        for (i=0; i<sizeof(atuple->rand); i++)
+                atuple->rand[i] = random() & 0xff;
+
+	switch (ainfo.auth_algo) {
+	case AUTH_ALGO_NONE:
+		DEBUGP(DMM, "No authentication for subscriber\n");
+		return 0;
+
+	case AUTH_ALGO_XOR:
+		if (_use_xor(&ainfo, atuple))
+			return 0;
+		break;
+
+	case AUTH_ALGO_COMP128v1:
+		if (_use_comp128_v1(&ainfo, atuple))
+			return 0;
+		break;
+
+	default:
+		DEBUGP(DMM, "Unsupported auth type algo_id=%d\n",
+			ainfo.auth_algo);
+		return 0;
+	}
+
+        db_sync_lastauthtuple_for_subscr(atuple, subscr);
+
+	DEBUGP(DMM, "Need to do authentication and ciphering\n");
+	return AUTH_DO_AUTH_THAN_CIPH;
+}
+
diff --git a/src/libmsc/db.c b/src/libmsc/db.c
new file mode 100644
index 0000000..95a7d36
--- /dev/null
+++ b/src/libmsc/db.c
@@ -0,0 +1,1303 @@
+/* Simple HLR/VLR database backend using dbi */
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <dbi/dbi.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/statistics.h>
+#include <osmocore/rate_ctr.h>
+
+static char *db_basename = NULL;
+static char *db_dirname = NULL;
+static dbi_conn conn;
+
+static char *create_stmts[] = {
+	"CREATE TABLE IF NOT EXISTS Meta ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"key TEXT UNIQUE NOT NULL, "
+		"value TEXT NOT NULL"
+		")",
+	"INSERT OR IGNORE INTO Meta "
+		"(key, value) "
+		"VALUES "
+		"('revision', '2')",
+	"CREATE TABLE IF NOT EXISTS Subscriber ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"imsi NUMERIC UNIQUE NOT NULL, "
+		"name TEXT, "
+		"extension TEXT UNIQUE, "
+		"authorized INTEGER NOT NULL DEFAULT 0, "
+		"tmsi TEXT UNIQUE, "
+		"lac INTEGER NOT NULL DEFAULT 0"
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthToken ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"subscriber_id INTEGER UNIQUE NOT NULL, "
+		"created TIMESTAMP NOT NULL, "
+		"token TEXT UNIQUE NOT NULL"
+		")",
+	"CREATE TABLE IF NOT EXISTS Equipment ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"name TEXT, "
+		"classmark1 NUMERIC, "
+		"classmark2 BLOB, "
+		"classmark3 BLOB, "
+		"imei NUMERIC UNIQUE NOT NULL"
+		")",
+	"CREATE TABLE IF NOT EXISTS EquipmentWatch ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC NOT NULL, "
+		"equipment_id NUMERIC NOT NULL, "
+		"UNIQUE (subscriber_id, equipment_id) "
+		")",
+	"CREATE TABLE IF NOT EXISTS SMS ("
+		/* metadata, not part of sms */
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"sent TIMESTAMP, "
+		"sender_id INTEGER NOT NULL, "
+		"receiver_id INTEGER NOT NULL, "
+		"deliver_attempts INTEGER NOT NULL DEFAULT 0, "
+		/* data directly copied/derived from SMS */
+		"valid_until TIMESTAMP, "
+		"reply_path_req INTEGER NOT NULL, "
+		"status_rep_req INTEGER NOT NULL, "
+		"protocol_id INTEGER NOT NULL, "
+		"data_coding_scheme INTEGER NOT NULL, "
+		"ud_hdr_ind INTEGER NOT NULL, "
+		"dest_addr TEXT, "
+		"user_data BLOB, "	/* TP-UD */
+		/* additional data, interpreted from SMS */
+		"header BLOB, "		/* UD Header */
+		"text TEXT "		/* decoded UD after UDH */
+		")",
+	"CREATE TABLE IF NOT EXISTS VLR ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC UNIQUE NOT NULL, "
+		"last_bts NUMERIC NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS ApduBlobs ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"apdu_id_flags INTEGER NOT NULL, "
+		"subscriber_id INTEGER NOT NULL, "
+		"apdu BLOB "
+		")",
+	"CREATE TABLE IF NOT EXISTS Counters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS RateCounters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL, "
+		"idx INTEGER NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthKeys ("
+		"subscriber_id INTEGER PRIMARY KEY, "
+		"algorithm_id INTEGER NOT NULL, "
+		"a3a8_ki BLOB "
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthLastTuples ("
+		"subscriber_id INTEGER PRIMARY KEY, "
+		"issued TIMESTAMP NOT NULL, "
+		"use_count INTEGER NOT NULL DEFAULT 0, "
+		"key_seq INTEGER NOT NULL, "
+		"rand BLOB NOT NULL, "
+		"sres BLOB NOT NULL, "
+		"kc BLOB NOT NULL "
+		")",
+};
+
+void db_error_func(dbi_conn conn, void *data)
+{
+	const char *msg;
+	dbi_conn_error(conn, &msg);
+	LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg);
+}
+
+static int check_db_revision(void)
+{
+	dbi_result result;
+	const char *rev;
+
+	result = dbi_conn_query(conn,
+				"SELECT value FROM Meta WHERE key='revision'");
+	if (!result)
+		return -EINVAL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+	rev = dbi_result_get_string(result, "value");
+	if (!rev || atoi(rev) != 2) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_init(const char *name)
+{
+	dbi_initialize(NULL);
+
+	conn = dbi_conn_new("sqlite3");
+	if (conn == NULL) {
+		LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n");
+		return 1;
+	}
+
+	dbi_conn_error_handler( conn, db_error_func, NULL );
+
+	/* MySQL
+	dbi_conn_set_option(conn, "host", "localhost");
+	dbi_conn_set_option(conn, "username", "your_name");
+	dbi_conn_set_option(conn, "password", "your_password");
+	dbi_conn_set_option(conn, "dbname", "your_dbname");
+	dbi_conn_set_option(conn, "encoding", "UTF-8");
+	*/
+
+	/* SqLite 3 */
+	db_basename = strdup(name);
+	db_dirname = strdup(name);
+	dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname));
+	dbi_conn_set_option(conn, "dbname", basename(db_basename));
+
+	if (dbi_conn_connect(conn) < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	free(db_dirname);
+	free(db_basename);
+	db_dirname = db_basename = NULL;
+	return -1;
+}
+
+
+int db_prepare()
+{
+	dbi_result result;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+		result = dbi_conn_query(conn, create_stmts[i]);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR,
+			     "Failed to create some table.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+	}
+
+	if (check_db_revision() < 0) {
+		LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, "
+			"please update your database schema\n");
+                return -1;
+	}
+
+	return 0;
+}
+
+int db_fini()
+{
+	dbi_conn_close(conn);
+	dbi_shutdown();
+
+	if (db_dirname)
+	    free(db_dirname);
+	if (db_basename)
+	    free(db_basename);
+	return 0;
+}
+
+struct gsm_subscriber *db_create_subscriber(struct gsm_network *net, char *imsi)
+{
+	dbi_result result;
+	struct gsm_subscriber *subscr;
+
+	/* Is this subscriber known in the db? */
+	subscr = db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi);
+	if (subscr) {
+		result = dbi_conn_queryf(conn,
+                         "UPDATE Subscriber set updated = datetime('now') "
+                         "WHERE imsi = %s " , imsi);
+		if (!result)
+			LOGP(DDB, LOGL_ERROR, "failed to update timestamp\n");
+		else
+			dbi_result_free(result);
+		return subscr;
+	}
+
+	subscr = subscr_alloc();
+	subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
+	if (!subscr)
+		return NULL;
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Subscriber "
+		"(imsi, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imsi
+	);
+	if (!result)
+		LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n");
+	subscr->net = net;
+	subscr->id = dbi_conn_sequence_last(conn, NULL);
+	strncpy(subscr->imsi, imsi, GSM_IMSI_LENGTH-1);
+	dbi_result_free(result);
+	LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
+	db_subscriber_alloc_exten(subscr);
+	return subscr;
+}
+
+static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size);
+
+static int get_equipment_by_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const char *string;
+	unsigned char cm1;
+	const unsigned char *cm2, *cm3;
+	struct gsm_equipment *equip = &subscr->equipment;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT Equipment.* "
+			"FROM Equipment JOIN EquipmentWatch ON "
+				"EquipmentWatch.equipment_id=Equipment.id "
+			"WHERE EquipmentWatch.subscriber_id = %llu "
+			"ORDER BY EquipmentWatch.updated DESC", subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	equip->id = dbi_result_get_ulonglong(result, "id");
+
+	string = dbi_result_get_string(result, "imei");
+	if (string)
+		strncpy(equip->imei, string, sizeof(equip->imei));
+
+	string = dbi_result_get_string(result, "classmark1");
+	if (string) {
+		cm1 = atoi(string) & 0xff;
+		memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1));
+	}
+
+	equip->classmark2_len = dbi_result_get_field_length(result, "classmark2");
+	cm2 = dbi_result_get_binary(result, "classmark2");
+	if (equip->classmark2_len > sizeof(equip->classmark2))
+		equip->classmark2_len = sizeof(equip->classmark2);
+	memcpy(equip->classmark2, cm2, equip->classmark2_len);
+
+	equip->classmark3_len = dbi_result_get_field_length(result, "classmark3");
+	cm3 = dbi_result_get_binary(result, "classmark3");
+	if (equip->classmark3_len > sizeof(equip->classmark3))
+		equip->classmark3_len = sizeof(equip->classmark3);
+	memcpy(equip->classmark3, cm3, equip->classmark3_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                               struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const unsigned char *a3a8_ki;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthKeys WHERE subscriber_id=%llu",
+			 subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id");
+	ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki");
+	a3a8_ki = dbi_result_get_binary(result, "a3a8_ki");
+	if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki))
+		ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki_len);
+	memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                                struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_auth_info ainfo_old;
+	int rc, upd;
+	unsigned char *ki_str;
+
+	/* Deletion ? */
+	if (ainfo == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthKeys WHERE subscriber_id=%llu",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = db_get_authinfo_for_subscr(&ainfo_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthKeys "
+				"(subscriber_id, algorithm_id, a3a8_ki) "
+				"VALUES (%llu, %u, %s)",
+				subscr->id, ainfo->auth_algo, ki_str);
+	} else {
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthKeys "
+				"SET algorithm_id=%u, a3a8_ki=%s "
+				"WHERE subscriber_id=%llu",
+				ainfo->auth_algo, ki_str, subscr->id);
+	}
+
+	free(ki_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                    struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int len;
+	const unsigned char *blob;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	memset(atuple, 0, sizeof(atuple));
+
+	atuple->use_count = dbi_result_get_ulonglong(result, "use_count");
+	atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq");
+
+	len = dbi_result_get_field_length(result, "rand");
+	if (len != sizeof(atuple->rand))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "rand");
+	memcpy(atuple->rand, blob, len);
+
+	len = dbi_result_get_field_length(result, "sres");
+	if (len != sizeof(atuple->sres))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "sres");
+	memcpy(atuple->sres, blob, len);
+
+	len = dbi_result_get_field_length(result, "kc");
+	if (len != sizeof(atuple->kc))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "kc");
+	memcpy(atuple->kc, blob, len);
+
+	dbi_result_free(result);
+
+	return 0;
+
+err_size:
+	dbi_result_free(result);
+	return -EIO;
+}
+
+int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                     struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int rc, upd;
+	struct gsm_auth_tuple atuple_old;
+	unsigned char *rand_str, *sres_str, *kc_str;
+
+	/* Deletion ? */
+	if (atuple == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = db_get_lastauthtuple_for_subscr(&atuple_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		atuple->rand, sizeof(atuple->rand), &rand_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->sres, sizeof(atuple->sres), &sres_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->kc, sizeof(atuple->kc), &kc_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthLastTuples "
+				"(subscriber_id, issued, use_count, "
+				 "key_seq, rand, sres, kc) "
+				"VALUES (%llu, datetime('now'), %u, "
+				 "%u, %s, %s, %s ) ",
+				subscr->id, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str);
+	} else {
+		char *issued = atuple->key_seq == atuple_old.key_seq ?
+					"issued" : "datetime('now')";
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthLastTuples "
+				"SET issued=%s, use_count=%u, "
+				 "key_seq=%u, rand=%s, sres=%s, kc=%s "
+				"WHERE subscriber_id = %llu",
+				issued, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str, subscr->id);
+	}
+
+	free(rand_str);
+	free(sres_str);
+	free(kc_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+static void db_set_from_query(struct gsm_subscriber *subscr, dbi_conn result)
+{
+	const char *string;
+	string = dbi_result_get_string(result, "imsi");
+	if (string)
+		strncpy(subscr->imsi, string, GSM_IMSI_LENGTH);
+
+	string = dbi_result_get_string(result, "tmsi");
+	if (string)
+		subscr->tmsi = tmsi_from_string(string);
+
+	string = dbi_result_get_string(result, "name");
+	if (string)
+		strncpy(subscr->name, string, GSM_NAME_LENGTH);
+
+	string = dbi_result_get_string(result, "extension");
+	if (string)
+		strncpy(subscr->extension, string, GSM_EXTENSION_LENGTH);
+
+	subscr->lac = dbi_result_get_uint(result, "lac");
+	subscr->authorized = dbi_result_get_uint(result, "authorized");
+}
+
+#define BASE_QUERY "SELECT * FROM Subscriber "
+struct gsm_subscriber *db_get_subscriber(struct gsm_network *net,
+					 enum gsm_subscriber_field field,
+					 const char *id)
+{
+	dbi_result result;
+	char *quoted;
+	struct gsm_subscriber *subscr;
+
+	switch (field) {
+	case GSM_SUBSCRIBER_IMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE imsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_TMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE tmsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_EXTENSION:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE extension = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_ID:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s ", quoted);
+		free(quoted);
+		break;
+	default:
+		LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n");
+		return NULL;
+	}
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n");
+		return NULL;
+	}
+	if (!dbi_result_next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n",
+			field, id);
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	subscr = subscr_alloc();
+	subscr->net = net;
+	subscr->id = dbi_result_get_ulonglong(result, "id");
+
+	db_set_from_query(subscr, result);
+	DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %u, EXTEN '%s', LAC %hu, AUTH %u\n",
+		subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension,
+		subscr->lac, subscr->authorized);
+	dbi_result_free(result);
+
+	get_equipment_by_subscr(subscr);
+
+	return subscr;
+}
+
+int db_subscriber_update(struct gsm_subscriber *subscr)
+{
+	char buf[32];
+	dbi_result result;
+
+	/* Copy the id to a string as queryf with %llu is failing */
+	sprintf(buf, "%llu", subscr->id);
+	result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s", buf);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber: %llu\n", subscr->id);
+		return -EIO;
+	}
+	if (!dbi_result_next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. %llu\n",
+			subscr->id);
+		dbi_result_free(result);
+		return -EIO;
+	}
+
+	db_set_from_query(subscr, result);
+	dbi_result_free(result);
+	get_equipment_by_subscr(subscr);
+
+	return 0;
+}
+
+int db_sync_subscriber(struct gsm_subscriber *subscriber)
+{
+	dbi_result result;
+	char tmsi[14];
+	char *q_tmsi, *q_name, *q_extension;
+
+	dbi_conn_quote_string_copy(conn, 
+				   subscriber->name, &q_name);
+	dbi_conn_quote_string_copy(conn, 
+				   subscriber->extension, &q_extension);
+	
+	if (subscriber->tmsi != GSM_RESERVED_TMSI) {
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn,
+				   tmsi,
+				   &q_tmsi);
+	} else
+		q_tmsi = strdup("NULL");
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Subscriber "
+		"SET updated = datetime('now'), "
+		"name = %s, "
+		"extension = %s, "
+		"authorized = %i, "
+		"tmsi = %s, "
+		"lac = %i "
+		"WHERE imsi = %s ",
+		q_name,
+		q_extension,
+		subscriber->authorized,
+		q_tmsi,
+		subscriber->lac,
+		subscriber->imsi);
+
+	free(q_tmsi);
+	free(q_name);
+	free(q_extension);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n");
+		return 1;
+	}
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_sync_equipment(struct gsm_equipment *equip)
+{
+	dbi_result result;
+	unsigned char *cm2, *cm3;
+	char *q_imei;
+	u_int8_t classmark1;
+
+	memcpy(&classmark1, &equip->classmark1, sizeof(classmark1));
+	DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x",
+		equip->imei, classmark1);
+	if (equip->classmark2_len)
+		DEBUGPC(DDB, ", classmark2=%s",
+			hexdump(equip->classmark2, equip->classmark2_len));
+	if (equip->classmark3_len)
+		DEBUGPC(DDB, ", classmark3=%s",
+			hexdump(equip->classmark3, equip->classmark3_len));
+	DEBUGPC(DDB, "\n");
+
+	dbi_conn_quote_binary_copy(conn, equip->classmark2,
+				   equip->classmark2_len, &cm2);
+	dbi_conn_quote_binary_copy(conn, equip->classmark3,
+				   equip->classmark3_len, &cm3);
+	dbi_conn_quote_string_copy(conn, equip->imei, &q_imei);
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Equipment SET "
+			"updated = datetime('now'), "
+			"classmark1 = %u, "
+			"classmark2 = %s, "
+			"classmark3 = %s "
+		"WHERE imei = %s ",
+		classmark1, cm2, cm3, q_imei);
+
+	free(cm2);
+	free(cm3);
+	free(q_imei);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n");
+		return -EIO;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	char tmsi[14];
+	char *tmsi_quoted;
+
+	for (;;) {
+		subscriber->tmsi = rand();
+		if (subscriber->tmsi == GSM_RESERVED_TMSI)
+			continue;
+
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE tmsi = %s ",
+			tmsi_quoted);
+
+		free(tmsi_quoted);
+
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new TMSI.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n",
+				subscriber->tmsi, subscriber->imsi);
+			return db_sync_subscriber(subscriber);
+		}
+		dbi_result_free(result);
+	}
+	return 0;
+}
+
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	u_int32_t try;
+
+	for (;;) {
+		try = (rand()%(GSM_MAX_EXTEN-GSM_MIN_EXTEN+1)+GSM_MIN_EXTEN);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE extension = %i",
+			try
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new extension.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)){
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	sprintf(subscriber->extension, "%i", try);
+	DEBUGP(DDB, "Allocated extension %i for IMSI %s.\n", try, subscriber->imsi);
+	return db_sync_subscriber(subscriber);
+}
+/*
+ * try to allocate a new unique token for this subscriber and return it
+ * via a parameter. if the subscriber already has a token, return
+ * an error.
+ */
+
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, u_int32_t *token)
+{
+	dbi_result result;
+	u_int32_t try;
+
+	for (;;) {
+		try = rand();
+		if (!try) /* 0 is an invalid token */
+			continue;
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthToken "
+			"WHERE subscriber_id = %llu OR token = \"%08X\" ",
+			subscriber->id, try);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken "
+				"while allocating new token.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO AuthToken "
+		"(subscriber_id, created, token) "
+		"VALUES "
+		"(%llu, datetime('now'), \"%08X\") ",
+		subscriber->id, try);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for "
+			"IMSI %s.\n", try, subscriber->imsi);
+		return 1;
+	}
+	dbi_result_free(result);
+	*token = try;
+	DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi);
+
+	return 0;
+}
+
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM_IMEI_LENGTH])
+{
+	unsigned long long equipment_id, watch_id;
+	dbi_result result;
+
+	strncpy(subscriber->equipment.imei, imei,
+		sizeof(subscriber->equipment.imei)-1),
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO Equipment "
+		"(imei, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imei);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n");
+		return 1;
+	}
+
+	equipment_id = 0;
+	if (dbi_result_get_numrows_affected(result)) {
+		equipment_id = dbi_conn_sequence_last(conn, NULL);
+	}
+	dbi_result_free(result);
+
+	if (equipment_id)
+		DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"SELECT id FROM Equipment "
+			"WHERE imei = %s ",
+			imei
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n");
+			return 1;
+		}
+		if (!dbi_result_next_row(result)) {
+			LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n");
+			dbi_result_free(result);
+			return 1;
+		}
+		equipment_id = dbi_result_get_ulonglong(result, "id");
+		dbi_result_free(result);
+	}
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO EquipmentWatch "
+		"(subscriber_id, equipment_id, created, updated) "
+		"VALUES "
+		"(%llu, %llu, datetime('now'), datetime('now')) ",
+		subscriber->id, equipment_id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n");
+		return 1;
+	}
+
+	watch_id = 0;
+	if (dbi_result_get_numrows_affected(result))
+		watch_id = dbi_conn_sequence_last(conn, NULL);
+
+	dbi_result_free(result);
+	if (watch_id)
+		DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"UPDATE EquipmentWatch "
+			"SET updated = datetime('now') "
+			"WHERE subscriber_id = %llu AND equipment_id = %llu ",
+			subscriber->id, equipment_id);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+		DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	}
+
+	return 0;
+}
+
+/* store an [unsent] SMS to the database */
+int db_sms_store(struct gsm_sms *sms)
+{
+	dbi_result result;
+	char *q_text, *q_daddr;
+	unsigned char *q_udata;
+	char *validity_timestamp = "2222-2-2";
+
+	/* FIXME: generate validity timestamp based on validity_minutes */
+
+	dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
+	dbi_conn_quote_string_copy(conn, (char *)sms->dest_addr, &q_daddr);
+	dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
+				   &q_udata);
+	/* FIXME: correct validity period */
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO SMS "
+		"(created, sender_id, receiver_id, valid_until, "
+		 "reply_path_req, status_rep_req, protocol_id, "
+		 "data_coding_scheme, ud_hdr_ind, dest_addr, "
+		 "user_data, text) VALUES "
+		"(datetime('now'), %llu, %llu, %u, "
+		 "%u, %u, %u, %u, %u, %s, %s, %s)",
+		sms->sender->id,
+		sms->receiver ? sms->receiver->id : 0, validity_timestamp,
+		sms->reply_path_req, sms->status_rep_req, sms->protocol_id,
+		sms->data_coding_scheme, sms->ud_hdr_ind,
+		q_daddr, q_udata, q_text);
+	free(q_text);
+	free(q_daddr);
+	free(q_udata);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result)
+{
+	struct gsm_sms *sms = sms_alloc();
+	long long unsigned int sender_id, receiver_id;
+	const char *text, *daddr;
+	const unsigned char *user_data;
+
+	if (!sms)
+		return NULL;
+
+	sms->id = dbi_result_get_ulonglong(result, "id");
+
+	sender_id = dbi_result_get_ulonglong(result, "sender_id");
+	sms->sender = subscr_get_by_id(net, sender_id);
+
+	receiver_id = dbi_result_get_ulonglong(result, "receiver_id");
+	sms->receiver = subscr_get_by_id(net, receiver_id);
+
+	/* FIXME: validity */
+	/* FIXME: those should all be get_uchar, but sqlite3 is braindead */
+	sms->reply_path_req = dbi_result_get_uint(result, "reply_path_req");
+	sms->status_rep_req = dbi_result_get_uint(result, "status_rep_req");
+	sms->ud_hdr_ind = dbi_result_get_uint(result, "ud_hdr_ind");
+	sms->protocol_id = dbi_result_get_uint(result, "protocol_id");
+	sms->data_coding_scheme = dbi_result_get_uint(result,
+						  "data_coding_scheme");
+	/* sms->msg_ref is temporary and not stored in DB */
+
+	daddr = dbi_result_get_string(result, "dest_addr");
+	if (daddr) {
+		strncpy(sms->dest_addr, daddr, sizeof(sms->dest_addr));
+		sms->dest_addr[sizeof(sms->dest_addr)-1] = '\0';
+	}
+
+	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+	user_data = dbi_result_get_binary(result, "user_data");
+	if (sms->user_data_len > sizeof(sms->user_data))
+		sms->user_data_len = (u_int8_t) sizeof(sms->user_data);
+	memcpy(sms->user_data, user_data, sms->user_data_len);
+
+	text = dbi_result_get_string(result, "text");
+	if (text) {
+		strncpy(sms->text, text, sizeof(sms->text));
+		sms->text[sizeof(sms->text)-1] = '\0';
+	}
+	return sms;
+}
+
+struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS WHERE SMS.id = %llu", id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS with ID >= min_id */
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.receiver_id = Subscriber.id "
+			"WHERE SMS.id >= %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 "
+			"ORDER BY SMS.id LIMIT 1",
+		min_id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net,
+					    unsigned long long min_subscr_id,
+					    unsigned int failed)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.receiver_id = Subscriber.id "
+			"WHERE SMS.receiver_id >= %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u "
+			"ORDER BY SMS.receiver_id, SMS.id LIMIT 1",
+		min_subscr_id, failed);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS for a given subscriber */
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.receiver_id = Subscriber.id "
+			"WHERE SMS.receiver_id = %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 "
+			"ORDER BY SMS.id LIMIT 1",
+		subscr->id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(subscr->net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* mark a given SMS as read */
+int db_sms_mark_sent(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET sent = datetime('now') "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+/* increase the number of attempted deliveries */
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET deliver_attempts = deliver_attempts + 1 "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for "
+			"SMS %llu.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_apdu_blob_store(struct gsm_subscriber *subscr,
+			u_int8_t apdu_id_flags, u_int8_t len,
+			u_int8_t *apdu)
+{
+	dbi_result result;
+	unsigned char *q_apdu;
+
+	dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO ApduBlobs "
+		"(created,subscriber_id,apdu_id_flags,apdu) VALUES "
+		"(datetime('now'),%llu,%u,%s)",
+		subscr->id, apdu_id_flags, q_apdu);
+
+	free(q_apdu);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_counter(struct counter *ctr)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctr->name, &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Counters "
+		"(timestamp,name,value) VALUES "
+		"(datetime('now'),%s,%lu)", q_name, ctr->value);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num,
+			     char *q_prefix)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name,
+				   &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"Insert INTO RateCounters "
+		"(timestamp,name,idx,value) VALUES "
+		"(datetime('now'),%s.%s,%u,%"PRIu64")",
+		q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg)
+{
+	unsigned int i;
+	char *q_prefix;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix);
+
+	for (i = 0; i < ctrg->desc->num_ctr; i++)
+		db_store_rate_ctr(ctrg, i, q_prefix);
+
+	free(q_prefix);
+
+	return 0;
+}
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
new file mode 100644
index 0000000..2b61aa9
--- /dev/null
+++ b/src/libmsc/gsm_04_08.c
@@ -0,0 +1,3345 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <openbsc/auth.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/transaction.h>
+#include <openbsc/ussd.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/bsc_api.h>
+#include <openbsc/osmo_msc.h>
+#include <osmocore/bitvec.h>
+
+#include <osmocore/gsm48.h>
+#include <osmocore/gsm0480.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <osmocore/tlv.h>
+
+void *tall_locop_ctx;
+void *tall_authciphop_ctx;
+
+int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, u_int32_t tmsi);
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+			   u_int8_t pdisc, u_int8_t msg_type);
+static void schedule_reject(struct gsm_subscriber_connection *conn);
+static void release_anchor(struct gsm_subscriber_connection *conn);
+
+struct gsm_lai {
+	u_int16_t mcc;
+	u_int16_t mnc;
+	u_int16_t lac;
+};
+
+static u_int32_t new_callref = 0x80000001;
+
+void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg)
+{
+	net->mncc_recv(net, msg);
+}
+
+static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection *conn,
+			      struct gsm_trans *trans)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
+
+	/* if we get passed a transaction reference, do some common
+	 * work that the caller no longer has to do */
+	if (trans) {
+		gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
+		msg->lchan = trans->conn->lchan;
+	}
+
+
+	if (msg->lchan) {
+		msg->trx = msg->lchan->ts->trx;
+		if ((gh->proto_discr & GSM48_PDISC_MASK) == GSM48_PDISC_CC)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) "
+				"Sending '%s' to MS.\n", msg->trx->bts->nr,
+				msg->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr & 0xf0,
+				gsm48_cc_msg_name(gh->msg_type));
+		else
+			DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) "
+				"Sending 0x%02x to MS.\n", msg->trx->bts->nr,
+				msg->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr, gh->msg_type);
+	}
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *ss_notify;
+
+	ss_notify = gsm0480_create_notifySS(message);
+	if (!ss_notify)
+		return -1;
+
+	gsm0480_wrap_invoke(ss_notify, GSM0480_OP_CODE_NOTIFY_SS, 0);
+	uint8_t *data = msgb_push(ss_notify, 1);
+	data[0] = ss_notify->len - 1;
+	gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh));
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+	return gsm48_conn_sendmsg(ss_notify, trans->conn, trans);
+}
+
+static void release_security_operation(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->sec_operation)
+		return;
+
+	talloc_free(conn->sec_operation);
+	conn->sec_operation = NULL;
+	msc_release_connection(conn);
+}
+
+static void allocate_security_operation(struct gsm_subscriber_connection *conn)
+{
+	conn->sec_operation = talloc_zero(tall_authciphop_ctx,
+	                                  struct gsm_security_operation);
+}
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+                         gsm_cbfn *cb, void *cb_data)
+{
+	struct gsm_network *net = conn->bts->network;
+	struct gsm_subscriber *subscr = conn->subscr;
+	struct gsm_security_operation *op;
+	struct gsm_auth_tuple atuple;
+	int status = -1, rc;
+
+	/* Check if we _can_ enable encryption. Cases where we can't:
+	 *  - Encryption disabled in config
+	 *  - Channel already secured (nothing to do)
+	 *  - Subscriber equipment doesn't support configured encryption
+	 */
+	if (!net->a5_encryption) {
+		status = GSM_SECURITY_NOAVAIL;
+	} else if (conn->lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		DEBUGP(DMM, "Requesting to secure an already secure channel");
+		status = GSM_SECURITY_SUCCEEDED;
+	} else if (!ms_cm2_a5n_support(subscr->equipment.classmark2,
+	                               net->a5_encryption)) {
+		DEBUGP(DMM, "Subscriber equipment doesn't support requested encryption");
+		status = GSM_SECURITY_NOAVAIL;
+	}
+
+	/* If not done yet, try to get info for this user */
+	if (status < 0) {
+		rc = auth_get_tuple_for_subscr(&atuple, subscr, key_seq);
+		if (rc <= 0)
+			status = GSM_SECURITY_NOAVAIL;
+	}
+
+	/* Are we done yet ? */
+	if (status >= 0)
+		return cb ?
+			cb(GSM_HOOK_RR_SECURITY, status, NULL, conn, cb_data) :
+			0;
+
+	/* Start an operation (can't have more than one pending !!!) */
+	if (conn->sec_operation)
+		return -EBUSY;
+
+	allocate_security_operation(conn);
+	op = conn->sec_operation;
+	op->cb = cb;
+	op->cb_data = cb_data;
+	memcpy(&op->atuple, &atuple, sizeof(struct gsm_auth_tuple));
+
+		/* FIXME: Should start a timer for completion ... */
+
+	/* Then do whatever is needed ... */
+	if (rc == AUTH_DO_AUTH_THAN_CIPH) {
+		/* Start authentication */
+		return gsm48_tx_mm_auth_req(conn, op->atuple.rand, op->atuple.key_seq);
+	} else if (rc == AUTH_DO_CIPH) {
+		/* Start ciphering directly */
+		return gsm0808_cipher_mode(conn, net->a5_encryption,
+		                           op->atuple.kc, 8, 0);
+	}
+
+	return -EINVAL; /* not reached */
+}
+
+static int authorize_subscriber(struct gsm_loc_updating_operation *loc,
+				struct gsm_subscriber *subscriber)
+{
+	if (!subscriber)
+		return 0;
+
+	/*
+	 * Do not send accept yet as more information should arrive. Some
+	 * phones will not send us the information and we will have to check
+	 * what we want to do with that.
+	 */
+	if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei))
+		return 0;
+
+	switch (subscriber->net->auth_policy) {
+	case GSM_AUTH_POLICY_CLOSED:
+		return subscriber->authorized;
+	case GSM_AUTH_POLICY_TOKEN:
+		if (subscriber->authorized)
+			return subscriber->authorized;
+		return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT);
+	case GSM_AUTH_POLICY_ACCEPT_ALL:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static void release_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->loc_operation)
+		return;
+
+	/* No need to keep the connection up */
+	release_anchor(conn);
+
+	bsc_del_timer(&conn->loc_operation->updating_timer);
+	talloc_free(conn->loc_operation);
+	conn->loc_operation = NULL;
+	msc_release_connection(conn);
+}
+
+static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	if (conn->loc_operation)
+		LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n");
+	release_loc_updating_req(conn);
+
+	conn->loc_operation = talloc_zero(tall_locop_ctx,
+					   struct gsm_loc_updating_operation);
+}
+
+static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event,
+                                     struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	int rc = 0;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			release_loc_updating_req(conn);
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_SUCCEEDED:
+			/* We're all good */
+			db_subscriber_alloc_tmsi(conn->subscr);
+			rc = gsm0408_loc_upd_acc(conn, conn->subscr->tmsi);
+			if (conn->bts->network->send_mm_info) {
+				/* send MM INFO with network name */
+				rc = gsm48_tx_mm_info(conn);
+			}
+
+			/* call subscr_update after putting the loc_upd_acc
+			 * in the transmit queue, since S_SUBSCR_ATTACHED might
+			 * trigger further action like SMS delivery */
+			subscr_update(conn->subscr, conn->bts,
+				      GSM_SUBSCRIBER_UPDATE_ATTACHED);
+
+			/*
+			 * The gsm0408_loc_upd_acc sends a MI with the TMSI. The
+			 * MS needs to respond with a TMSI REALLOCATION COMPLETE
+			 * (even if the TMSI is the same).
+			 */
+			break;
+
+		default:
+			rc = -EINVAL;
+	};
+
+	return rc;
+}
+
+static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	if (authorize_subscriber(conn->loc_operation, conn->subscr))
+		return gsm48_secure_channel(conn,
+			conn->loc_operation->key_seq,
+			_gsm0408_authorize_sec_cb, NULL);
+	return 0;
+}
+
+void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	struct gsm_trans *trans, *temp;
+
+	/* avoid someone issuing a clear */
+	conn->in_release = 1;
+
+	/*
+	 * Cancel any outstanding location updating request
+	 * operation taking place on the subscriber connection.
+	 */
+	release_loc_updating_req(conn);
+
+	/* We might need to cancel the paging response or such. */
+	if (conn->sec_operation && conn->sec_operation->cb) {
+		conn->sec_operation->cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+					NULL, conn, conn->sec_operation->cb_data);
+	}
+
+	release_security_operation(conn);
+	release_anchor(conn);
+
+	/* Free all transactions that are associated with the released lchan */
+	/* FIXME: this is not neccessarily the right thing to do, we should
+	 * only set trans->lchan to NULL and wait for another lchan to be
+	 * established to the same MM entity (phone/subscriber) */
+	llist_for_each_entry_safe(trans, temp, &conn->bts->network->trans_list, entry) {
+		if (trans->conn == conn)
+			trans_free(trans);
+	}
+}
+
+void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
+{
+	struct gsm_trans *trans, *temp;
+
+	LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n");
+
+	llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) {
+		if (trans->protocol == protocol) {
+			trans->callref = 0;
+			trans_free(trans);
+		}
+	}
+}
+
+/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
+int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, u_int8_t cause)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct msgb *msg;
+
+	counter_inc(bts->network->stats.loc_upd_resp.reject);
+
+	msg = gsm48_create_loc_upd_rej(cause);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+		return -1;
+	}
+	
+	msg->lchan = conn->lchan;
+
+	LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT "
+	     "LAC=%u BTS=%u\n", conn->subscr ?
+	     			subscr_name(conn->subscr) : "unknown",
+	     bts->location_area_code, bts->nr);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
+int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, u_int32_t tmsi)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_loc_area_id *lai;
+	u_int8_t *mid;
+	
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT;
+
+	lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
+	gsm48_generate_lai(lai, bts->network->country_code,
+		     bts->network->network_code, bts->location_area_code);
+
+	mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+	gsm48_generate_mid_from_tmsi(mid, tmsi);
+
+	DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
+
+	counter_inc(bts->network->stats.loc_upd_resp.accept);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Transmit Chapter 9.2.10 Identity Request */
+static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, u_int8_t id_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_ID_REQ;
+	gh->data[0] = id_type;
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+
+/* Parse Chapter 9.2.11 Identity Response */
+static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_network *net = bts->network;
+	u_int8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+	DEBUGP(DMM, "IDENTITY RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* look up subscriber based on IMSI, create if not found */
+		if (!conn->subscr) {
+			conn->subscr = subscr_get_by_imsi(net, mi_string);
+			if (!conn->subscr)
+				conn->subscr = db_create_subscriber(net, mi_string);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imsi = 0;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* update subscribe <-> IMEI mapping */
+		if (conn->subscr) {
+			db_subscriber_assoc_imei(conn->subscr, mi_string);
+			db_sync_equipment(&conn->subscr->equipment);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imei = 0;
+		break;
+	}
+
+	/* Check if we can let the mobile station enter */
+	return gsm0408_authorize(conn, msg);
+}
+
+
+static void loc_upd_rej_cb(void *data)
+{
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm_lchan *lchan = conn->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+
+	gsm0408_loc_upd_rej(conn, bts->network->reject_cause);
+	release_loc_updating_req(conn);
+}
+
+static void schedule_reject(struct gsm_subscriber_connection *conn)
+{
+	conn->loc_operation->updating_timer.cb = loc_upd_rej_cb;
+	conn->loc_operation->updating_timer.data = conn;
+	bsc_schedule_timer(&conn->loc_operation->updating_timer, 5, 0);
+}
+
+static const char *lupd_name(u_int8_t type)
+{
+	switch (type) {
+	case GSM48_LUPD_NORMAL:
+		return "NORMAL";
+	case GSM48_LUPD_PERIODIC:
+		return "PEROIDOC";
+	case GSM48_LUPD_IMSI_ATT:
+		return "IMSI ATTACH";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+/* Chapter 9.2.15: Receive Location Updating Request */
+static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_loc_upd_req *lu;
+	struct gsm_subscriber *subscr = NULL;
+	struct gsm_bts *bts = conn->bts;
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	int rc;
+
+ 	lu = (struct gsm48_loc_upd_req *) gh->data;
+
+	mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+
+	DEBUGPC(DMM, "mi_type=0x%02x MI(%s) type=%s ", mi_type, mi_string,
+		lupd_name(lu->type));
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
+
+	switch (lu->type) {
+	case GSM48_LUPD_NORMAL:
+		counter_inc(bts->network->stats.loc_upd_type.normal);
+		break;
+	case GSM48_LUPD_IMSI_ATT:
+		counter_inc(bts->network->stats.loc_upd_type.attach);
+		break;
+	case GSM48_LUPD_PERIODIC:
+		counter_inc(bts->network->stats.loc_upd_type.periodic);
+		break;
+	}
+
+	/*
+	 * Pseudo Spoof detection: Just drop a second/concurrent
+	 * location updating request.
+	 */
+	if (conn->loc_operation) {
+		DEBUGPC(DMM, "ignoring request due an existing one: %p.\n",
+			conn->loc_operation);
+		gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR);
+		return 0;
+	}
+
+	allocate_loc_updating_req(conn);
+
+	conn->loc_operation->key_seq = lu->key_seq;
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		DEBUGPC(DMM, "\n");
+		/* we always want the IMEI, too */
+		rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+
+		/* look up subscriber based on IMSI, create if not found */
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		if (!subscr) {
+			subscr = db_create_subscriber(bts->network, mi_string);
+		}
+		break;
+	case GSM_MI_TYPE_TMSI:
+		DEBUGPC(DMM, "\n");
+		/* look up the subscriber based on TMSI, request IMSI if it fails */
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		if (!subscr) {
+			/* send IDENTITY REQUEST message to get IMSI */
+			rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI);
+			conn->loc_operation->waiting_for_imsi = 1;
+		}
+		/* we always want the IMEI, too */
+		rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:	
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	/* schedule the reject timer */
+	schedule_reject(conn);
+
+	if (!subscr) {
+		DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+
+	conn->subscr = subscr;
+	conn->subscr->equipment.classmark1 = lu->classmark1;
+
+	/* check if we can let the subscriber into our network immediately
+	 * or if we need to wait for identity responses. */
+	return gsm0408_authorize(conn, msg);
+}
+
+#if 0
+static u_int8_t to_bcd8(u_int8_t val)
+{
+       return ((val / 10) << 4) | (val % 10);
+}
+#endif
+
+/* Section 9.2.15a */
+int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm_network *net = conn->bts->network;
+	u_int8_t *ptr8;
+	int name_len, name_pad;
+#if 0
+	time_t cur_t;
+	struct tm* cur_time;
+	int tz15min;
+#endif
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_INFO;
+
+	if (net->name_long) {
+#if 0
+		name_len = strlen(net->name_long);
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len*2 +1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (u_int16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_long[i]);
+
+		/* FIXME: Use Cell Broadcast, not UCS-2, since
+		 * UCS-2 is only supported by later revisions of the spec */
+#endif
+		name_len = (strlen(net->name_long)*7)/8;
+		name_pad = (8 - strlen(net->name_long)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode(ptr8, net->name_long);
+
+	}
+
+	if (net->name_short) {
+#if 0
+		name_len = strlen(net->name_short);
+		/* 10.5.3.5a */
+		ptr8 = (u_int8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len*2 + 1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (u_int16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_short[i]);
+#endif
+		name_len = (strlen(net->name_short)*7)/8;
+		name_pad = (8 - strlen(net->name_short)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = (u_int8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode(ptr8, net->name_short);
+
+	}
+
+#if 0
+	/* Section 10.5.3.9 */
+	cur_t = time(NULL);
+	cur_time = gmtime(&cur_t);
+	ptr8 = msgb_put(msg, 8);
+	ptr8[0] = GSM48_IE_NET_TIME_TZ;
+	ptr8[1] = to_bcd8(cur_time->tm_year % 100);
+	ptr8[2] = to_bcd8(cur_time->tm_mon);
+	ptr8[3] = to_bcd8(cur_time->tm_mday);
+	ptr8[4] = to_bcd8(cur_time->tm_hour);
+	ptr8[5] = to_bcd8(cur_time->tm_min);
+	ptr8[6] = to_bcd8(cur_time->tm_sec);
+	/* 02.42: coded as BCD encoded signed value in units of 15 minutes */
+	tz15min = (cur_time->tm_gmtoff)/(60*15);
+	ptr8[7] = to_bcd8(tz15min);
+	if (tz15min < 0)
+		ptr8[7] |= 0x80;
+#endif
+
+	DEBUGP(DMM, "-> MM INFO\n");
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Section 9.2.2 */
+int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, u_int8_t *rand, int key_seq)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar));
+
+	DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", hexdump(rand, 16));
+
+	msg->lchan = conn->lchan;
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_AUTH_REQ;
+
+	ar->key_seq = key_seq;
+
+	/* 16 bytes RAND parameters */
+	if (rand)
+		memcpy(ar->rand, rand, 16);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Section 9.2.1 */
+int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn)
+{
+	DEBUGP(DMM, "-> AUTH REJECT\n");
+	return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
+}
+
+static int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn)
+{
+	DEBUGP(DMM, "-> CM SERVICE ACK\n");
+	return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_ACC);
+}
+
+/* 9.2.6 CM service reject */
+static int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+				enum gsm48_reject_value value)
+{
+	struct msgb *msg;
+
+	msg = gsm48_create_mm_serv_rej(value);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n");
+		return -1;
+	}
+
+	DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value);
+	msg->lchan = conn->lchan;
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static int _gsm48_rx_mm_serv_req_sec_cb(
+	unsigned int hooknum, unsigned int event,
+	struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	int rc = 0;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			/* Nothing to do */
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+			rc = gsm48_tx_mm_serv_ack(conn);
+			break;
+
+		case GSM_SECURITY_SUCCEEDED:
+			/* nothing to do. CIPHER MODE COMMAND is
+			 * implicit CM SERV ACK */
+			break;
+
+		default:
+			rc = -EINVAL;
+	};
+
+	return rc;
+}
+
+/*
+ * Handle CM Service Requests
+ * a) Verify that the packet is long enough to contain the information
+ *    we require otherwsie reject with INCORRECT_MESSAGE
+ * b) Try to parse the TMSI. If we do not have one reject
+ * c) Check that we know the subscriber with the TMSI otherwise reject
+ *    with a HLR cause
+ * d) Set the subscriber on the gsm_lchan and accept
+ */
+static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+
+	struct gsm_bts *bts = conn->bts;
+	struct gsm_subscriber *subscr;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_service_request *req =
+			(struct gsm48_service_request *)gh->data;
+	/* unfortunately in Phase1 the classmark2 length is variable */
+	u_int8_t classmark2_len = gh->data[1];
+	u_int8_t *classmark2 = gh->data+2;
+	u_int8_t mi_len = *(classmark2 + classmark2_len);
+	u_int8_t *mi = (classmark2 + classmark2_len + 1);
+
+	DEBUGP(DMM, "<- CM SERVICE REQUEST ");
+	if (msg->data_len < sizeof(struct gsm48_service_request*)) {
+		DEBUGPC(DMM, "wrong sized message\n");
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	if (msg->data_len < req->mi_len + 6) {
+		DEBUGPC(DMM, "does not fit in packet\n");
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	mi_type = mi[0] & GSM_MI_TYPE_MASK;
+	if (mi_type != GSM_MI_TYPE_TMSI) {
+		DEBUGPC(DMM, "mi_type is not TMSI: %d\n", mi_type);
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+	DEBUGPC(DMM, "serv_type=0x%02x mi_type=0x%02x M(%s)\n",
+		req->cm_service_type, mi_type, mi_string);
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len));
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2-1);
+
+	subscr = subscr_get_by_tmsi(bts->network,
+				    tmsi_from_string(mi_string));
+
+	/* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */
+	if (!subscr)
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_IMSI_UNKNOWN_IN_HLR);
+
+	if (!conn->subscr)
+		conn->subscr = subscr;
+	else if (conn->subscr == subscr)
+		subscr_put(subscr); /* lchan already has a ref, don't need another one */
+	else {
+		DEBUGP(DMM, "<- CM Channel already owned by someone else?\n");
+		subscr_put(subscr);
+	}
+
+	subscr->equipment.classmark2_len = classmark2_len;
+	memcpy(subscr->equipment.classmark2, classmark2, classmark2_len);
+	db_sync_equipment(&subscr->equipment);
+
+	return gsm48_secure_channel(conn, req->cipher_key_seq,
+			_gsm48_rx_mm_serv_req_sec_cb, NULL);
+}
+
+static int gsm48_rx_mm_imsi_detach_ind(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_imsi_detach_ind *idi =
+				(struct gsm48_imsi_detach_ind *) gh->data;
+	u_int8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len);
+	DEBUGP(DMM, "IMSI DETACH INDICATION: mi_type=0x%02x MI(%s): ",
+		mi_type, mi_string);
+
+	counter_inc(bts->network->stats.loc_upd_type.detach);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:	
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	if (subscr) {
+		subscr_update(subscr, msg->trx->bts,
+				GSM_SUBSCRIBER_UPDATE_DETACHED);
+		DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr));
+
+		subscr->equipment.classmark1 = idi->classmark1;
+		db_sync_equipment(&subscr->equipment);
+
+		subscr_put(subscr);
+	} else
+		DEBUGP(DMM, "Unknown Subscriber ?!?\n");
+
+	/* FIXME: iterate over all transactions and release them,
+	 * imagine an IMSI DETACH happening during an active call! */
+
+	/* subscriber is detached: should we release lchan? */
+	return 0;
+}
+
+static int gsm48_rx_mm_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]);
+
+	return 0;
+}
+
+/* Chapter 9.2.3: Authentication Response */
+static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data;
+	struct gsm_network *net = conn->bts->network;
+
+	DEBUGP(DMM, "MM AUTHENTICATION RESPONSE (sres = %s): ",
+		hexdump(ar->sres, 4));
+
+	/* Safety check */
+	if (!conn->sec_operation) {
+		DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n");
+		return -EIO;
+	}
+
+	/* Validate SRES */
+	if (memcmp(conn->sec_operation->atuple.sres, ar->sres,4)) {
+		int rc;
+		gsm_cbfn *cb = conn->sec_operation->cb;
+
+		DEBUGPC(DMM, "Invalid (expected %s)\n",
+			hexdump(conn->sec_operation->atuple.sres, 4));
+
+		if (cb)
+			cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+			   NULL, conn, conn->sec_operation->cb_data);
+
+		rc = gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return rc;
+	}
+
+	DEBUGPC(DMM, "OK\n");
+
+	/* Start ciphering */
+	return gsm0808_cipher_mode(conn, net->a5_encryption,
+	                           conn->sec_operation->atuple.kc, 8, 0);
+}
+
+/* Receive a GSM 04.08 Mobility Management (MM) message */
+static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type & 0xbf) {
+	case GSM48_MT_MM_LOC_UPD_REQUEST:
+		DEBUGP(DMM, "LOCATION UPDATING REQUEST: ");
+		rc = mm_rx_loc_upd_req(conn, msg);
+		break;
+	case GSM48_MT_MM_ID_RESP:
+		rc = mm_rx_id_resp(conn, msg);
+		break;
+	case GSM48_MT_MM_CM_SERV_REQ:
+		rc = gsm48_rx_mm_serv_req(conn, msg);
+		break;
+	case GSM48_MT_MM_STATUS:
+		rc = gsm48_rx_mm_status(msg);
+		break;
+	case GSM48_MT_MM_TMSI_REALL_COMPL:
+		DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+		       conn->subscr ?
+				subscr_name(conn->subscr) :
+				"unknown subscriber");
+		release_loc_updating_req(conn);
+		break;
+	case GSM48_MT_MM_IMSI_DETACH_IND:
+		rc = gsm48_rx_mm_imsi_detach_ind(msg);
+		break;
+	case GSM48_MT_MM_CM_REEST_REQ:
+		DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n");
+		break;
+	case GSM48_MT_MM_AUTH_RESP:
+		rc = gsm48_rx_mm_auth_resp(conn, msg);
+		break;
+	default:
+		LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+/* Receive a PAGING RESPONSE message from the MS */
+static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_pag_resp *resp;
+	u_int8_t *classmark2_lv = gh->data + 1;
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+	int rc = 0;
+
+	resp = (struct gsm48_pag_resp *) &gh->data[0];
+	gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+				mi_string, &mi_type);
+	DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		break;
+	}
+
+	if (!subscr) {
+		DEBUGP(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+	DEBUGP(DRR, "<- Channel was requested by %s\n",
+		subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi);
+
+	subscr->equipment.classmark2_len = *classmark2_lv;
+	memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv);
+	db_sync_equipment(&subscr->equipment);
+
+	rc = gsm48_handle_paging_resp(conn, msg, subscr);
+	return rc;
+}
+
+static int gsm48_rx_rr_classmark(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_subscriber *subscr = conn->subscr;
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	u_int8_t cm2_len, cm3_len = 0;
+	u_int8_t *cm2, *cm3 = NULL;
+
+	DEBUGP(DRR, "CLASSMARK CHANGE ");
+
+	/* classmark 2 */
+	cm2_len = gh->data[0];
+	cm2 = &gh->data[1];
+	DEBUGPC(DRR, "CM2(len=%u) ", cm2_len);
+
+	if (payload_len > cm2_len + 1) {
+		/* we must have a classmark3 */
+		if (gh->data[cm2_len+1] != 0x20) {
+			DEBUGPC(DRR, "ERR CM3 TAG\n");
+			return -EINVAL;
+		}
+		if (cm2_len > 3) {
+			DEBUGPC(DRR, "CM2 too long!\n");
+			return -EINVAL;
+		}
+		
+		cm3_len = gh->data[cm2_len+2];
+		cm3 = &gh->data[cm2_len+3];
+		if (cm3_len > 14) {
+			DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len);
+			return -EINVAL;
+		}
+		DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len);
+	}
+	if (subscr) {
+		subscr->equipment.classmark2_len = cm2_len;
+		memcpy(subscr->equipment.classmark2, cm2, cm2_len);
+		if (cm3) {
+			subscr->equipment.classmark3_len = cm3_len;
+			memcpy(subscr->equipment.classmark3, cm3, cm3_len);
+		}
+		db_sync_equipment(&subscr->equipment);
+	}
+
+	return 0;
+}
+
+static int gsm48_rx_rr_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "STATUS rr_cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	return 0;
+}
+
+static int gsm48_rx_rr_meas_rep(struct msgb *msg)
+{
+	struct gsm_meas_rep *meas_rep = lchan_next_meas_rep(msg->lchan);
+
+	/* This shouldn't actually end up here, as RSL treats
+	 * L3 Info of 08.58 MEASUREMENT REPORT different by calling
+	 * directly into gsm48_parse_meas_rep */
+	DEBUGP(DMEAS, "DIRECT GSM48 MEASUREMENT REPORT ?!? ");
+	gsm48_parse_meas_rep(meas_rep, msg);
+
+	return 0;
+}
+
+static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t apdu_id_flags;
+	u_int8_t apdu_len;
+	u_int8_t *apdu_data;
+
+	apdu_id_flags = gh->data[0];
+	apdu_len = gh->data[1];
+	apdu_data = gh->data+2;
+	
+	DEBUGP(DNM, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s",
+		apdu_id_flags, apdu_len, hexdump(apdu_data, apdu_len));
+
+	return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data);
+}
+
+/* Chapter 9.1.10 Ciphering Mode Complete */
+static int gsm48_rx_rr_ciph_m_compl(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	gsm_cbfn *cb;
+	int rc = 0;
+
+	DEBUGP(DRR, "CIPHERING MODE COMPLETE\n");
+
+	/* Safety check */
+	if (!conn->sec_operation) {
+		DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n");
+		return -EIO;
+	}
+
+	/* FIXME: check for MI (if any) */
+
+	/* Call back whatever was in progress (if anything) ... */
+	cb = conn->sec_operation->cb;
+	if (cb) {
+		rc = cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED,
+			NULL, conn, conn->sec_operation->cb_data);
+	}
+
+	/* Complete the operation */
+	release_security_operation(conn);
+
+	return rc;
+}
+
+/* Chapter 9.1.16 Handover complete */
+static int gsm48_rx_rr_ho_compl(struct msgb *msg)
+{
+	struct lchan_signal_data sig;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	sig.lchan = msg->lchan;
+	sig.mr = NULL;
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig);
+	/* FIXME: release old channel */
+
+	return 0;
+}
+
+/* Chapter 9.1.17 Handover Failure */
+static int gsm48_rx_rr_ho_fail(struct msgb *msg)
+{
+	struct lchan_signal_data sig;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER FAILED cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	sig.lchan = msg->lchan;
+	sig.mr = NULL;
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig);
+	/* FIXME: release allocated new channel */
+
+	return 0;
+}
+
+/* Receive a GSM 04.08 Radio Resource (RR) message */
+static int gsm0408_rcv_rr(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_RR_CLSM_CHG:
+		rc = gsm48_rx_rr_classmark(conn, msg);
+		break;
+	case GSM48_MT_RR_GPRS_SUSP_REQ:
+		DEBUGP(DRR, "GRPS SUSPEND REQUEST\n");
+		break;
+	case GSM48_MT_RR_PAG_RESP:
+		rc = gsm48_rx_rr_pag_resp(conn, msg);
+		break;
+	case GSM48_MT_RR_STATUS:
+		rc = gsm48_rx_rr_status(msg);
+		break;
+	case GSM48_MT_RR_MEAS_REP:
+		rc = gsm48_rx_rr_meas_rep(msg);
+		break;
+	case GSM48_MT_RR_APP_INFO:
+		rc = gsm48_rx_rr_app_info(conn, msg);
+		break;
+	case GSM48_MT_RR_CIPH_M_COMPL:
+		rc = gsm48_rx_rr_ciph_m_compl(conn, msg);
+		break;
+	case GSM48_MT_RR_HANDO_COMPL:
+		rc = gsm48_rx_rr_ho_compl(msg);
+		break;
+	case GSM48_MT_RR_HANDO_FAIL:
+		rc = gsm48_rx_rr_ho_fail(msg);
+		break;
+	default:
+		LOGP(DRR, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 RR msg type 0x%02x\n", gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, u_int8_t apdu_id,
+			   u_int8_t apdu_len, const u_int8_t *apdu)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = conn->lchan;
+	
+	DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n",
+		apdu_id, apdu_len);
+	
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_APP_INFO;
+	gh->data[0] = apdu_id;
+	gh->data[1] = apdu_len;
+	memcpy(gh->data+2, apdu, apdu_len);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Call Control */
+
+/* The entire call control code is written in accordance with Figure 7.10c
+ * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE
+ * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY
+ * it for voice */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+	if (state > 31 || state < 0)
+		return;
+
+	DEBUGP(DCC, "new state %s -> %s\n",
+		gsm48_cc_state_name(trans->cc.state),
+		gsm48_cc_state_name(state));
+
+	trans->cc.state = state;
+}
+
+static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	u_int8_t *cause, *call_state;
+
+	gh->msg_type = GSM48_MT_CC_STATUS;
+
+	cause = msgb_put(msg, 3);
+	cause[0] = 2;
+	cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER;
+	cause[2] = 0x80 | 30;	/* response to status inquiry */
+
+	call_state = msgb_put(msg, 1);
+	call_state[0] = 0xc0 | 0x00;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+			   u_int8_t pdisc, u_int8_t msg_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	msg->lchan = conn->lchan;
+
+	gh->proto_discr = pdisc;
+	gh->msg_type = msg_type;
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+	if (bsc_timer_pending(&trans->cc.timer)) {
+		DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent);
+		bsc_del_timer(&trans->cc.timer);
+		trans->cc.Tcurrent = 0;
+	}
+}
+
+static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans,
+			int msg_type, struct gsm_mncc *mncc)
+{
+	struct msgb *msg;
+	unsigned char *data;
+
+	if (trans)
+		if (trans->conn && trans->conn->lchan)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+				"Sending '%s' to MNCC.\n",
+				trans->conn->lchan->ts->trx->bts->nr,
+				trans->conn->lchan->ts->trx->nr,
+				trans->conn->lchan->ts->nr, trans->transaction_id,
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+		else
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Sending '%s' to MNCC.\n",
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+	else
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) "
+			"Sending '%s' to MNCC.\n", get_mncc_name(msg_type));
+
+	mncc->msg_type = msg_type;
+	
+	msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+	if (!msg)
+		return -ENOMEM;
+
+	data = msgb_put(msg, sizeof(struct gsm_mncc));
+	memcpy(data, mncc, sizeof(struct gsm_mncc));
+
+	cc_tx_to_mncc(net, msg);
+
+	return 0;
+}
+
+int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans,
+		     u_int32_t callref, int location, int value)
+{
+	struct gsm_mncc rel;
+
+	memset(&rel, 0, sizeof(rel));
+	rel.callref = callref;
+	mncc_set_cause(&rel, location, value);
+	return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+}
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF! */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+	gsm48_stop_cc_timer(trans);
+
+	/* send release to L4, if callref still exists */
+	if (trans->callref) {
+		/* Ressource unavailable */
+		mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				 GSM48_CAUSE_LOC_PRN_S_LU,
+				 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+	}
+	if (trans->cc.state != GSM_CSTATE_NULL)
+		new_cc_state(trans, GSM_CSTATE_NULL);
+	if (trans->conn)
+		trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref);
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+
+/* call-back from paging the B-end of the connection */
+static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_conn, void *param)
+{
+	int found = 0;
+	struct gsm_subscriber_connection *conn = _conn;
+	struct gsm_network **paging_request = param, *net;
+	struct gsm_trans *transt, *tmp;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	net = *paging_request;
+	if (!net) {
+		DEBUGP(DCC, "Error Network not set!\n");
+		return -EINVAL;
+	}
+
+	/* check all tranactions (without lchan) for subscriber */
+	llist_for_each_entry_safe(transt, tmp, &net->trans_list, entry) {
+		if (transt->paging_request != paging_request || transt->conn)
+			continue;
+		switch (event) {
+		case GSM_PAGING_SUCCEEDED:
+			if (!conn) // paranoid
+				break;
+			DEBUGP(DCC, "Paging subscr %s succeeded!\n",
+				transt->subscr->extension);
+			found = 1;
+			/* Assign lchan */
+			if (!transt->conn) {
+				transt->paging_request = NULL;
+				transt->conn = conn;
+				conn->put_channel = 1;
+			}
+			/* send SETUP request to called party */
+			gsm48_cc_tx_setup(transt, &transt->cc.msg);
+			break;
+		case GSM_PAGING_EXPIRED:
+		case GSM_PAGING_BUSY:
+			DEBUGP(DCC, "Paging subscr %s expired!\n",
+				transt->subscr->extension);
+			/* Temporarily out of order */
+			found = 1;
+			mncc_release_ind(transt->subscr->net, transt,
+					 transt->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_DEST_OOO);
+			transt->callref = 0;
+			transt->paging_request = NULL;
+			trans_free(transt);
+			break;
+		}
+	}
+
+	talloc_free(paging_request);
+
+	/*
+	 * FIXME: The queue needs to be kicked. This is likely to go through a RF
+	 * failure and then the subscr will be poke again. This needs a lot of fixing
+	 * in the subscriber queue code.
+	 */
+	if (!found && conn)
+		conn->put_channel = 1;
+	return 0;
+}
+
+static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable);
+
+/* handle audio path for handover */
+static int handle_ho_signal(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct rtp_socket *old_rs, *new_rs, *other_rs;
+	struct ho_signal_data *sig = signal_data;
+
+	if (subsys != SS_HO || signal != S_HANDOVER_ACK)
+		return 0;
+
+	if (ipacc_rtp_direct) {
+		LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n");
+		return 0;
+	}
+
+	/* RTP Proxy mode */
+	new_rs = sig->new_lchan->abis_ip.rtp_socket;
+	old_rs = sig->old_lchan->abis_ip.rtp_socket;
+
+	if (!new_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n");
+		return -EIO;
+	}
+
+	rsl_ipacc_mdcx_to_rtpsock(sig->new_lchan);
+
+	if (!old_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for old_lchan\n");
+		return -EIO;
+	}
+
+	/* copy rx_action and reference to other sock */
+	new_rs->rx_action = old_rs->rx_action;
+	new_rs->tx_action = old_rs->tx_action;
+	new_rs->transmit = old_rs->transmit;
+
+	switch (sig->old_lchan->abis_ip.rtp_socket->rx_action) {
+	case RTP_PROXY:
+		other_rs = old_rs->proxy.other_sock;
+		rtp_socket_proxy(new_rs, other_rs);
+		/* delete reference to other end socket to prevent
+		 * rtp_socket_free() from removing the inverse reference */
+		old_rs->proxy.other_sock = NULL;
+		break;
+	case RTP_RECV_UPSTREAM:
+		new_rs->receive = old_rs->receive;
+		break;
+	case RTP_NONE:
+		break;
+	}
+
+	return 0;
+}
+
+/* some other part of the code sends us a signal */
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+				 void *handler_data, void *signal_data)
+{
+	struct gsm_lchan *lchan = signal_data;
+	int rc;
+	struct gsm_network *net;
+	struct gsm_trans *trans;
+
+	if (subsys != SS_ABISIP)
+		return 0;
+
+	/* in case we use direct BTS-to-BTS RTP */
+	if (ipacc_rtp_direct)
+		return 0;
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		/* in case we don't use direct BTS-to-BTS RTP */
+		/* the BTS has successfully bound a TCH to a local ip/port,
+		 * which means we can connect our UDP socket to it */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		lchan->abis_ip.rtp_socket = rtp_socket_create();
+		if (!lchan->abis_ip.rtp_socket)
+			return -EIO;
+
+		rc = rtp_socket_connect(lchan->abis_ip.rtp_socket,
+				   lchan->abis_ip.bound_ip,
+				   lchan->abis_ip.bound_port);
+		if (rc < 0)
+			return -EIO;
+
+		/* check if any transactions on this lchan still have
+		 * a tch_recv_mncc request pending */
+		net = lchan->ts->trx->bts->network;
+		llist_for_each_entry(trans, &net->trans_list, entry) {
+			if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) {
+				DEBUGP(DCC, "pending tch_recv_mncc request\n");
+				tch_recv_mncc(net, trans->callref, 1);
+			}
+		}
+		break;
+	case S_ABISIP_DLCX_IND:
+		/* the BTS tells us a RTP stream has been disconnected */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+/* map two ipaccess RTP streams onto each other */
+static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts;
+	int rc;
+
+	DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u) and (bts=%u,trx=%u,ts=%u)\n",
+		bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr);
+
+	if (bts->type != remote_bts->type) {
+		DEBUGP(DCC, "Cannot switch calls between different BTS types yet\n");
+		return -EINVAL;
+	}
+
+	// todo: map between different bts types
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		if (!ipacc_rtp_direct) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan);
+			if (rc < 0)
+				return rc;
+			/* connect them with each other */
+			rtp_socket_proxy(lchan->abis_ip.rtp_socket,
+					 remote_lchan->abis_ip.rtp_socket);
+		} else {
+			/* directly connect TCH RTP streams to each other */
+			rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip,
+						remote_lchan->abis_ip.bound_port,
+						remote_lchan->abis_ip.rtp_payload2);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip,
+						lchan->abis_ip.bound_port,
+						lchan->abis_ip.rtp_payload2);
+		}
+		break;
+	case GSM_BTS_TYPE_BS11:
+		trau_mux_map_lchan(lchan, remote_lchan);
+		break;
+	default:
+		DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* bridge channels of two transactions */
+static int tch_bridge(struct gsm_network *net, u_int32_t *refs)
+{
+	struct gsm_trans *trans1 = trans_find_by_callref(net, refs[0]);
+	struct gsm_trans *trans2 = trans_find_by_callref(net, refs[1]);
+
+	if (!trans1 || !trans2)
+		return -EIO;
+
+	if (!trans1->conn || !trans2->conn)
+		return -EIO;
+
+	/* through-connect channel */
+	return tch_map(trans1->conn->lchan, trans2->conn->lchan);
+}
+
+/* enable receive of channels to MNCC upqueue */
+static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable)
+{
+	struct gsm_trans *trans;
+	struct gsm_lchan *lchan;
+	struct gsm_bts *bts;
+	int rc;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans)
+		return -EIO;
+	if (!trans->conn)
+		return 0;
+	lchan = trans->conn->lchan;
+	bts = lchan->ts->trx->bts;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		if (ipacc_rtp_direct) {
+			DEBUGP(DCC, "Error: RTP proxy is disabled\n");
+			return -EINVAL;
+		}
+		/* in case, we don't have a RTP socket yet, we note this
+		 * in the transaction and try later */
+		if (!lchan->abis_ip.rtp_socket) {
+			trans->tch_recv = enable;
+			DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+			return 0;
+		}
+		if (enable) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			/* assign socket to application interface */
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, callref);
+		} else
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, 0);
+		break;
+	case GSM_BTS_TYPE_BS11:
+		if (enable)
+			return trau_recv_lchan(lchan, callref);
+		return trau_mux_unmap(NULL, callref);
+		break;
+	default:
+		DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+	DEBUGP(DCC, "-> STATUS ENQ\n");
+	return gsm48_cc_tx_status(trans, msg);
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+
+static void gsm48_cc_timeout(void *arg)
+{
+	struct gsm_trans *trans = arg;
+	int disconnect = 0, release = 0;
+	int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+	int mo_location = GSM48_CAUSE_LOC_USER;
+	int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+	int l4_location = GSM48_CAUSE_LOC_PRN_S_LU;
+	struct gsm_mncc mo_rel, l4_rel;
+
+	memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+	mo_rel.callref = trans->callref;
+	memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+	l4_rel.callref = trans->callref;
+
+	switch(trans->cc.Tcurrent) {
+	case 0x303:
+		release = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x310:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x313:
+		disconnect = 1;
+		/* unknown, did not find it in the specs */
+		break;
+	case 0x301:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x308:
+		if (!trans->cc.T308_second) {
+			/* restart T308 a second time */
+			gsm48_cc_tx_release(trans, &trans->cc.msg);
+			trans->cc.T308_second = 1;
+			break; /* stay in release state */
+		}
+		trans_free(trans);
+		return;
+//		release = 1;
+//		l4_cause = 14;
+//		break;
+	case 0x306:
+		release = 1;
+		mo_cause = trans->cc.msg.cause.value;
+		mo_location = trans->cc.msg.cause.location;
+		break;
+	case 0x323:
+		disconnect = 1;
+		break;
+	default:
+		release = 1;
+	}
+
+	if (release && trans->callref) {
+		/* process release towards layer 4 */
+		mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				 l4_location, l4_cause);
+		trans->callref = 0;
+	}
+
+	if (disconnect && trans->callref) {
+		/* process disconnect towards layer 4 */
+		mncc_set_cause(&l4_rel, l4_location, l4_cause);
+		mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &l4_rel);
+	}
+
+	/* process disconnect towards mobile station */
+	if (disconnect || release) {
+		mncc_set_cause(&mo_rel, mo_location, mo_cause);
+		mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+		mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+		mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+		mo_rel.cause.diag_len = 3;
+
+		if (disconnect)
+			gsm48_cc_tx_disconnect(trans, &mo_rel);
+		if (release)
+			gsm48_cc_tx_release(trans, &mo_rel);
+	}
+
+}
+
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+				 int sec, int micro)
+{
+	DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec);
+	trans->cc.timer.cb = gsm48_cc_timeout;
+	trans->cc.timer.data = trans;
+	bsc_schedule_timer(&trans->cc.timer, sec, micro);
+	trans->cc.Tcurrent = current;
+}
+
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type & 0xbf;
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc setup;
+
+	memset(&setup, 0, sizeof(struct gsm_mncc));
+	setup.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* emergency setup is identified by msg_type */
+	if (msg_type == GSM48_MT_CC_EMERG_SETUP)
+		setup.emergency = 1;
+
+	/* use subscriber as calling party number */
+	if (trans->subscr) {
+		setup.fields |= MNCC_F_CALLING;
+		strncpy(setup.calling.number, trans->subscr->extension,
+			sizeof(setup.calling.number)-1);
+		strncpy(setup.imsi, trans->subscr->imsi,
+			sizeof(setup.imsi)-1);
+	}
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		setup.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&setup.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		setup.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&setup.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* called party bcd number */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+		setup.fields |= MNCC_F_CALLED;
+		gsm48_decode_called(&setup.called,
+			      TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		setup.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&setup.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		setup.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&setup.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+	/* CLIR suppression */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP))
+		setup.clir.sup = 1;
+	/* CLIR invocation */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC))
+		setup.clir.inv = 1;
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		setup.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&setup.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+	LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n",
+	     subscr_name(trans->subscr), trans->subscr->extension,
+	     setup.called.number);
+
+	counter_inc(trans->subscr->net->stats.call.mo_setup);
+
+	/* indicate setup to MNCC */
+	mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_IND, &setup);
+
+	/* MNCC code will modify the channel asynchronously, we should
+	 * ipaccess-bind only after the modification has been made to the
+	 * lchan->tch_mode */
+	return 0;
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm_mncc *setup = arg;
+	int rc, trans_id;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	/* transaction id must not be assigned */
+	if (trans->transaction_id != 0xff) { /* unasssigned */
+		DEBUGP(DCC, "TX Setup with assigned transaction. "
+			"This is not allowed!\n");
+		/* Temporarily out of order */
+		rc = mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	
+	/* Get free transaction_id */
+	trans_id = trans_assign_trans_id(trans->subscr, GSM48_PDISC_CC, 0);
+	if (trans_id < 0) {
+		/* no free transaction ID */
+		rc = mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	trans->transaction_id = trans_id;
+
+	gh->msg_type = GSM48_MT_CC_SETUP;
+
+	gsm48_start_cc_timer(trans, 0x303, GSM48_T303);
+
+	/* bearer capability */
+	if (setup->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap);
+	/* facility */
+	if (setup->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &setup->facility);
+	/* progress */
+	if (setup->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &setup->progress);
+	/* calling party BCD number */
+	if (setup->fields & MNCC_F_CALLING)
+		gsm48_encode_calling(msg, &setup->calling);
+	/* called party BCD number */
+	if (setup->fields & MNCC_F_CALLED)
+		gsm48_encode_called(msg, &setup->called);
+	/* user-user */
+	if (setup->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &setup->useruser);
+	/* redirecting party BCD number */
+	if (setup->fields & MNCC_F_REDIRECTING)
+		gsm48_encode_redirecting(msg, &setup->redirecting);
+	/* signal */
+	if (setup->fields & MNCC_F_SIGNAL)
+		gsm48_encode_signal(msg, setup->signal);
+	
+	new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+	counter_inc(trans->subscr->net->stats.call.mt_setup);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc call_conf;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x310, GSM48_T310);
+
+	memset(&call_conf, 0, sizeof(struct gsm_mncc));
+	call_conf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+	/* repeat */
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+		call_conf.repeat = 1;
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+		call_conf.repeat = 2;
+#endif
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		call_conf.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&call_conf.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		call_conf.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&call_conf.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		call_conf.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&call_conf.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_CALL_CONF_IND,
+			    &call_conf);
+}
+
+static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *proceeding = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CALL_PROC;
+
+	new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+	/* bearer capability */
+	if (proceeding->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap);
+	/* facility */
+	if (proceeding->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &proceeding->facility);
+	/* progress */
+	if (proceeding->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &proceeding->progress);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc alerting;
+	
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x301, GSM48_T301);
+
+	memset(&alerting, 0, sizeof(struct gsm_mncc));
+	alerting.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		alerting.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&alerting.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+
+	/* progress */
+	if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+		alerting.fields |= MNCC_F_PROGRESS;
+		gsm48_decode_progress(&alerting.progress,
+				TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		alerting.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&alerting.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_ALERT_IND,
+			    &alerting);
+}
+
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *alerting = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_ALERTING;
+
+	/* facility */
+	if (alerting->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &alerting->facility);
+	/* progress */
+	if (alerting->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &alerting->progress);
+	/* user-user */
+	if (alerting->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &alerting->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+	
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *progress = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_PROGRESS;
+
+	/* progress */
+	gsm48_encode_progress(msg, 1, &progress->progress);
+	/* user-user */
+	if (progress->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &progress->useruser);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *connect = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x313, GSM48_T313);
+
+	/* facility */
+	if (connect->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &connect->facility);
+	/* progress */
+	if (connect->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &connect->progress);
+	/* connected number */
+	if (connect->fields & MNCC_F_CONNECTED)
+		gsm48_encode_connected(msg, &connect->connected);
+	/* user-user */
+	if (connect->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &connect->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_IND);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc connect;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&connect, 0, sizeof(struct gsm_mncc));
+	connect.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* use subscriber as connected party number */
+	if (trans->subscr) {
+		connect.fields |= MNCC_F_CONNECTED;
+		strncpy(connect.connected.number, trans->subscr->extension,
+			sizeof(connect.connected.number)-1);
+		strncpy(connect.imsi, trans->subscr->imsi,
+			sizeof(connect.imsi)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		connect.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&connect.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		connect.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&connect.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		connect.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&connect.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+	counter_inc(trans->subscr->net->stats.call.mt_connect);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_CNF, &connect);
+}
+
+
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc connect_ack;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+	counter_inc(trans->subscr->net->stats.call.mo_connect_ack);
+	
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = trans->callref;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_COMPL_IND,
+			    &connect_ack);
+}
+
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc disc;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+	memset(&disc, 0, sizeof(struct gsm_mncc));
+	disc.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		disc.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&disc.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		disc.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&disc.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		disc.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&disc.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		disc.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&disc.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &disc);
+
+}
+
+static struct gsm_mncc_cause default_cause = {
+	.location	= GSM48_CAUSE_LOC_PRN_S_LU,
+	.coding		= 0,
+	.rec		= 0,
+	.rec_val	= 0,
+	.value		= GSM48_CC_CAUSE_NORMAL_UNSPEC,
+	.diag_len	= 0,
+	.diag		= { 0 },
+};
+
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *disc = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x306, GSM48_T306);
+
+	/* cause */
+	if (disc->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &disc->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	/* facility */
+	if (disc->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &disc->facility);
+	/* progress */
+	if (disc->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &disc->progress);
+	/* user-user */
+	if (disc->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &disc->useruser);
+
+	/* store disconnect cause for T306 expiry */
+	memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc));
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+		/* release collision 5.4.5 */
+		rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_CNF, &rel);
+	} else {
+		rc = gsm48_tx_simple(trans->conn,
+				     GSM48_PDISC_CC | (trans->transaction_id << 4),
+				     GSM48_MT_CC_RELEASE_COMPL);
+		rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_IND, &rel);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_NULL);
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RELEASE;
+
+	trans->callref = 0;
+	
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x308, GSM48_T308);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	trans->cc.T308_second = 0;
+	memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+	if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+		new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc = 0;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->callref) {
+		switch (trans->cc.state) {
+		case GSM_CSTATE_CALL_PRESENT:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REJ_IND, &rel);
+			break;
+		case GSM_CSTATE_RELEASE_REQ:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REL_CNF, &rel);
+			break;
+		default:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REL_IND, &rel);
+		}
+	}
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	int ret;
+
+	gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+	trans->callref = 0;
+	
+	gsm48_stop_cc_timer(trans);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	ret =  gsm48_conn_sendmsg(msg, trans->conn, trans);
+
+	trans_free(trans);
+
+	return ret;
+}
+
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc fac;
+
+	memset(&fac, 0, sizeof(struct gsm_mncc));
+	fac.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		fac.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&fac.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		fac.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&fac.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_FACILITY_IND, &fac);
+}
+
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *fac = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+
+	/* facility */
+	gsm48_encode_facility(msg, 1, &fac->facility);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc hold;
+
+	memset(&hold, 0, sizeof(struct gsm_mncc));
+	hold.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_HOLD_IND, &hold);
+}
+
+static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *hold_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_REJ;
+
+	/* cause */
+	if (hold_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &hold_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc retrieve;
+
+	memset(&retrieve, 0, sizeof(struct gsm_mncc));
+	retrieve.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_RETRIEVE_IND,
+			    &retrieve);
+}
+
+static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *retrieve_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_REJ;
+
+	/* cause */
+	if (retrieve_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &retrieve_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* keypad facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+		dtmf.fields |= MNCC_F_KEYPAD;
+		gsm48_decode_keypad(&dtmf.keypad,
+			      TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_START_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_ACK;
+
+	/* keypad */
+	if (dtmf->fields & MNCC_F_KEYPAD)
+		gsm48_encode_keypad(msg, dtmf->keypad);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_REJ;
+
+	/* cause */
+	if (dtmf->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &dtmf->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_STOP_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_IND, &modify);
+}
+
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY;
+
+	gsm48_start_cc_timer(trans, 0x323, GSM48_T323);
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= GSM48_IE_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		modify.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&modify.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+	/* cause */
+	gsm48_encode_cause(msg, 1, &modify->cause);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *notify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+	/* notify */
+	gsm48_encode_notify(msg, notify->notify);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+//	struct tlv_parsed tp;
+	struct gsm_mncc notify;
+
+	memset(&notify, 0, sizeof(struct gsm_mncc));
+	notify.callref = trans->callref;
+//	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len);
+	if (payload_len >= 1)
+		gsm48_decode_notify(&notify.notify, gh->data);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *user = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+	/* user-user */
+	if (user->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 1, &user->useruser);
+	/* more data */
+	if (user->more)
+		gsm48_encode_more(msg);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc user;
+
+	memset(&user, 0, sizeof(struct gsm_mncc));
+	user.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0);
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		user.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&user.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* more data */
+	if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+		user.more = 1;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_USERINFO_IND, &user);
+}
+
+static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *mode = arg;
+
+	return gsm0808_assign_req(trans->conn, mode->lchan_mode, 1);
+}
+
+static struct downstate {
+	u_int32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */
+	 MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */
+	 MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */
+	 MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+	{SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */
+	 MNCC_PROGRESS_REQ, gsm48_cc_tx_progress},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+	 MNCC_SETUP_REQ, gsm48_cc_tx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_REQUEST),
+	 MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack},
+	 /* signalling during call */
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+	 MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+	{ALL_STATES,
+	 MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack},
+	{ALL_STATES,
+	 MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej},
+	{ALL_STATES,
+	 MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+	/* clearing */
+	{SBIT(GSM_CSTATE_INITIATED),
+	 MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */
+	 MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 MNCC_REL_REQ, gsm48_cc_tx_release},
+	/* special */
+	{ALL_STATES,
+	 MNCC_LCHAN_MODIFY, _gsm48_lchan_modify},
+};
+
+#define DOWNSLLEN \
+	(sizeof(downstatelist) / sizeof(struct downstate))
+
+
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
+{
+	int i, rc = 0;
+	struct gsm_trans *trans = NULL, *transt;
+	struct gsm_subscriber_connection *conn = NULL;
+	struct gsm_bts *bts = NULL;
+	struct gsm_mncc *data = arg, rel;
+
+	DEBUGP(DMNCC, "receive message %s\n", get_mncc_name(msg_type));
+
+	/* handle special messages */
+	switch(msg_type) {
+	case MNCC_BRIDGE:
+		return tch_bridge(net, arg);
+	case MNCC_FRAME_DROP:
+		return tch_recv_mncc(net, data->callref, 0);
+	case MNCC_FRAME_RECV:
+		return tch_recv_mncc(net, data->callref, 1);
+	case GSM_TCHF_FRAME:
+		/* Find callref */
+		trans = trans_find_by_callref(net, data->callref);
+		if (!trans) {
+			LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n");
+			return -EIO;
+		}
+		if (!trans->conn) {
+			LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n");
+			return 0;
+		}
+		if (trans->conn->lchan->type != GSM_LCHAN_TCH_F) {
+			/* This should be LOGL_ERROR or NOTICE, but
+			 * unfortuantely it happens for a couple of frames at
+			 * the beginning of every RTP connection */
+			LOGP(DMNCC, LOGL_DEBUG, "TCH frame for lchan != TCH_F\n");
+			return 0;
+		}
+		bts = trans->conn->lchan->ts->trx->bts;
+		switch (bts->type) {
+		case GSM_BTS_TYPE_NANOBTS:
+			if (!trans->conn->lchan->abis_ip.rtp_socket) {
+				DEBUGP(DMNCC, "TCH frame to lchan without RTP connection\n");
+				return 0;
+			}
+			return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg);
+		case GSM_BTS_TYPE_BS11:
+			return trau_send_frame(trans->conn->lchan, arg);
+		default:
+			DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		}
+		return -EINVAL;
+	}
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = data->callref;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, data->callref);
+
+	/* Callref unknown */
+	if (!trans) {
+		struct gsm_subscriber *subscr;
+
+		if (msg_type != MNCC_SETUP_REQ) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown callref %d\n", data->called.number,
+				get_mncc_name(msg_type), data->callref);
+			/* Invalid call reference */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INVAL_TRANS_ID);
+		}
+		if (!data->called.number[0] && !data->imsi[0]) {
+			DEBUGP(DCC, "(bts - trx - ts - ti) "
+				"Received '%s' from MNCC with "
+				"no number or IMSI\n", get_mncc_name(msg_type));
+			/* Invalid number */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INV_NR_FORMAT);
+		}
+		/* New transaction due to setup, find subscriber */
+		if (data->called.number[0])
+			subscr = subscr_get_by_extension(net,
+							data->called.number);
+		else
+			subscr = subscr_get_by_imsi(net, data->imsi);
+		/* If subscriber is not found */
+		if (!subscr) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			/* Unknown subscriber */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_UNASSIGNED_NR);
+		}
+		/* If subscriber is not "attached" */
+		if (!subscr->lac) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"detached subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			subscr_put(subscr);
+			/* Temporarily out of order */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_DEST_OOO);
+		}
+		/* Create transaction */
+		trans = trans_alloc(subscr, GSM48_PDISC_CC, 0xff, data->callref);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			subscr_put(subscr);
+			/* Ressource unavailable */
+			mncc_release_ind(net, NULL, data->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			return -ENOMEM;
+		}
+		/* Find lchan */
+		conn = connection_for_subscr(subscr);
+
+		/* If subscriber has no lchan */
+		if (!conn) {
+			/* find transaction with this subscriber already paging */
+			llist_for_each_entry(transt, &net->trans_list, entry) {
+				/* Transaction of our lchan? */
+				if (transt == trans ||
+				    transt->subscr != subscr)
+					continue;
+				DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+					"Received '%s' from MNCC with "
+					"unallocated channel, paging already "
+					"started for lac %d.\n",
+					data->called.number,
+					get_mncc_name(msg_type), subscr->lac);
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+			/* store setup informations until paging was successfull */
+			memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+
+			/* Get a channel */
+			trans->paging_request = talloc_zero(subscr->net, struct gsm_network*);
+			if (!trans->paging_request) {
+				LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n");
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+
+			*trans->paging_request = subscr->net;
+			subscr_get_channel(subscr, RSL_CHANNEED_TCH_F, setup_trig_pag_evt, trans->paging_request);
+
+			subscr_put(subscr);
+			return 0;
+		}
+		/* Assign lchan */
+		trans->conn = conn;
+		subscr_put(subscr);
+	}
+
+	if (trans->conn)
+		conn = trans->conn;
+
+	/* if paging did not respond yet */
+	if (!conn) {
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+			"Received '%s' from MNCC in paging state\n",
+			(trans->subscr)?(trans->subscr->extension):"-",
+			get_mncc_name(msg_type));
+		mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_NORM_CALL_CLEAR);
+		if (msg_type == MNCC_REL_REQ)
+			rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+		else
+			rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) "
+		"Received '%s' from MNCC in state %d (%s)\n",
+		conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+		trans->transaction_id,
+		(trans->conn->subscr)?(trans->conn->subscr->extension):"-",
+		get_mncc_name(msg_type), trans->cc.state,
+		gsm48_cc_state_name(trans->cc.state));
+
+	/* Find function for current state and message */
+	for (i = 0; i < DOWNSLLEN; i++)
+		if ((msg_type == downstatelist[i].type)
+		 && ((1 << trans->cc.state) & downstatelist[i].states))
+			break;
+	if (i == DOWNSLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = downstatelist[i].rout(trans, arg);
+
+	return rc;
+}
+
+
+static struct datastate {
+	u_int32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */
+	 GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */
+	 GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */
+	 GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */
+	 GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+	 /* signalling during call */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL),
+	 GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+	{ALL_STATES,
+	 GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_HOLD, gsm48_cc_rx_hold},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+	/* clearing */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */
+	 GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+	{ALL_STATES, /* 5.4.3.4 */
+	 GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+};
+
+#define DATASLLEN \
+	(sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type & 0xbf;
+	u_int8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* flip */
+	struct gsm_trans *trans = NULL;
+	int i, rc = 0;
+
+	if (msg_type & 0x80) {
+		DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type);
+		return -EINVAL;
+	}
+
+	/* Find transaction */
+	trans = trans_find_by_id(conn->subscr, GSM48_PDISC_CC, transaction_id);
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+		"Received '%s' from MS in state %d (%s)\n",
+		conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+		transaction_id, (conn->subscr)?(conn->subscr->extension):"-",
+		gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
+		gsm48_cc_state_name(trans?(trans->cc.state):0));
+
+	/* Create transaction */
+	if (!trans) {
+		DEBUGP(DCC, "Unknown transaction ID %x, "
+			"creating new trans.\n", transaction_id);
+		/* Create transaction */
+		trans = trans_alloc(conn->subscr, GSM48_PDISC_CC,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			rc = gsm48_tx_simple(conn,
+					     GSM48_PDISC_CC | (transaction_id << 4),
+					     GSM48_MT_CC_RELEASE_COMPL);
+			return -ENOMEM;
+		}
+		/* Assign transaction */
+		trans->conn = conn;
+	}
+
+	/* find function for current state and message */
+	for (i = 0; i < DATASLLEN; i++)
+		if ((msg_type == datastatelist[i].type)
+		 && ((1 << trans->cc.state) & datastatelist[i].states))
+			break;
+	if (i == DATASLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = datastatelist[i].rout(trans, msg);
+
+	return rc;
+}
+
+/* Create a dummy to wait five seconds */
+static void release_anchor(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->anch_operation)
+		return;
+
+	bsc_del_timer(&conn->anch_operation->timeout);
+	talloc_free(conn->anch_operation);
+	conn->anch_operation = NULL;
+}
+
+static void anchor_timeout(void *_data)
+{
+	struct gsm_subscriber_connection *con = _data;
+
+	release_anchor(con);
+	msc_release_connection(con);
+}
+
+int gsm0408_new_conn(struct gsm_subscriber_connection *conn)
+{
+	conn->anch_operation = talloc_zero(conn, struct gsm_anchor_operation);
+	if (!conn->anch_operation)
+		return -1;
+
+	conn->anch_operation->timeout.data = conn;
+	conn->anch_operation->timeout.cb = anchor_timeout;
+	bsc_schedule_timer(&conn->anch_operation->timeout, 5, 0);
+	return 0;
+}
+
+/* here we get data from the BSC level... */
+int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t pdisc = gh->proto_discr & 0x0f;
+	int rc = 0;
+
+	if (silent_call_reroute(conn, msg))
+		return silent_call_rx(conn, msg);
+	
+	switch (pdisc) {
+	case GSM48_PDISC_CC:
+		release_anchor(conn);
+		rc = gsm0408_rcv_cc(conn, msg);
+		break;
+	case GSM48_PDISC_MM:
+		rc = gsm0408_rcv_mm(conn, msg);
+		break;
+	case GSM48_PDISC_RR:
+		rc = gsm0408_rcv_rr(conn, msg);
+		break;
+	case GSM48_PDISC_SMS:
+		release_anchor(conn);
+		rc = gsm0411_rcv_sms(conn, msg);
+		break;
+	case GSM48_PDISC_MM_GPRS:
+	case GSM48_PDISC_SM_GPRS:
+		LOGP(DRLL, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		break;
+	case GSM48_PDISC_NC_SS:
+		release_anchor(conn);
+		rc = handle_rcv_ussd(conn, msg);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "Unknown "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * This will be ran by the linker when loading the DSO. We use it to
+ * do system initialization, e.g. registration of signal handlers.
+ */
+static __attribute__((constructor)) void on_dso_load_0408(void)
+{
+	register_signal_handler(SS_HO, handle_ho_signal, NULL);
+	register_signal_handler(SS_ABISIP, handle_abisip_signal, NULL);
+}
diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c
new file mode 100644
index 0000000..812e758
--- /dev/null
+++ b/src/libmsc/gsm_04_11.c
@@ -0,0 +1,1240 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+#define GSM411_ALLOC_SIZE	1024
+#define GSM411_ALLOC_HEADROOM	128
+
+void *tall_gsms_ctx;
+static u_int32_t new_callref = 0x40000001;
+
+static const struct value_string cp_cause_strs[] = {
+	{ GSM411_CP_CAUSE_NET_FAIL,	"Network Failure" },
+	{ GSM411_CP_CAUSE_CONGESTION,	"Congestion" },
+	{ GSM411_CP_CAUSE_INV_TRANS_ID,	"Invalid Transaction ID" },
+	{ GSM411_CP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_CP_CAUSE_INV_MAND_INF,	"Invalid Mandatory Information" },
+	{ GSM411_CP_CAUSE_MSGTYPE_NOTEXIST, "Message Type doesn't exist" },
+	{ GSM411_CP_CAUSE_MSG_INCOMP_STATE,
+				"Message incompatible with protocol state" },
+	{ GSM411_CP_CAUSE_IE_NOTEXIST,	"IE does not exist" },
+	{ GSM411_CP_CAUSE_PROTOCOL_ERR,	"Protocol Error" },
+	{ 0, 0 }
+};
+
+static const struct value_string rp_cause_strs[] = {
+	{ GSM411_RP_CAUSE_MO_NUM_UNASSIGNED, "(MO) Number not assigned" },
+	{ GSM411_RP_CAUSE_MO_OP_DET_BARR, "(MO) Operator determined barring" },
+	{ GSM411_RP_CAUSE_MO_CALL_BARRED, "(MO) Call barred" },
+	{ GSM411_RP_CAUSE_MO_SMS_REJECTED, "(MO) SMS rejected" },
+	{ GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER, "(MO) Destination out of order" },
+	{ GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR, "(MO) Unidentified subscriber" },
+	{ GSM411_RP_CAUSE_MO_FACILITY_REJ, "(MO) Facility reject" },
+	{ GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR, "(MO) Unknown subscriber" },
+	{ GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER, "(MO) Network out of order" },
+	{ GSM411_RP_CAUSE_MO_TEMP_FAIL, "(MO) Temporary failure" },
+	{ GSM411_RP_CAUSE_MO_CONGESTION, "(MO) Congestion" },
+	{ GSM411_RP_CAUSE_MO_RES_UNAVAIL, "(MO) Resource unavailable" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR, "(MO) Requested facility not subscribed" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL, "(MO) Requested facility not implemented" },
+	{ GSM411_RP_CAUSE_MO_INTERWORKING, "(MO) Interworking" },
+	/* valid only for MT */
+	{ GSM411_RP_CAUSE_MT_MEM_EXCEEDED, "(MT) Memory Exceeded" },
+	/* valid for both directions */
+	{ GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" },
+	{ GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" },
+	{ GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" },
+	{ GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" },
+	{ GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" },
+	{ GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" },
+	{ 0, NULL }
+};
+
+
+struct gsm_sms *sms_alloc(void)
+{
+	return talloc_zero(tall_gsms_ctx, struct gsm_sms);
+}
+
+void sms_free(struct gsm_sms *sms)
+{
+	/* drop references to subscriber structure */
+	if (sms->sender)
+		subscr_put(sms->sender);
+	if (sms->receiver)
+		subscr_put(sms->receiver);
+
+	talloc_free(sms);
+}
+
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, int dcs, const char *text)
+{
+	struct gsm_sms *sms = sms_alloc();
+
+	if (!sms)
+		return NULL;
+
+	sms->receiver = subscr_get(receiver);
+	strncpy(sms->text, text, sizeof(sms->text)-1);
+
+	/* FIXME: don't use ID 1 static */
+	sms->sender = subscr_get_by_id(receiver->net, 1);
+	sms->reply_path_req = 0;
+	sms->status_rep_req = 0;
+	sms->ud_hdr_ind = 0;
+	sms->protocol_id = 0; /* implicit */
+	sms->data_coding_scheme = dcs;
+	strncpy(sms->dest_addr, receiver->extension, sizeof(sms->dest_addr)-1);
+	/* Generate user_data */
+	sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text);
+
+	return sms;
+}
+
+
+static void send_signal(int sig_no,
+			struct gsm_trans *trans,
+			struct gsm_sms *sms,
+			int paging_result)
+{
+	struct sms_signal_data sig;
+	sig.trans = trans;
+	sig.sms = sms;
+	sig.paging_result = paging_result;
+	dispatch_signal(SS_SMS, sig_no, &sig);
+}
+
+/*
+ * This should be called whenever all SMS to a given subscriber
+ * on a given connection has been sent. This will inform the higher
+ * layers that a channel can be given up.
+ */
+static void gsm411_release_conn(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+	subscr_put_channel(conn->subscr);
+}
+
+struct msgb *gsm411_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM411_ALLOC_SIZE, GSM411_ALLOC_HEADROOM,
+				   "GSM 04.11");
+}
+
+static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg, u_int8_t link_id)
+{
+	DEBUGP(DSMS, "GSM4.11 TX %s\n", hexdump(msg->data, msg->len));
+	msg->l3h = msg->data;
+	return gsm0808_submit_dtap(conn, msg, link_id, 1);
+}
+
+/* SMC TC1* is expired */
+static void cp_timer_expired(void *data)
+{
+	struct gsm_trans *trans = data;
+
+	DEBUGP(DSMS, "SMC Timer TC1* is expired, calling trans_free()\n");
+	/* FIXME: we need to re-transmit the last CP-DATA 1..3 times */
+	trans_free(trans);
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     u_int8_t msg_type)
+{
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	/* Outgoing needs the highest bit set */
+	gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
+	gh->msg_type = msg_type;
+
+	/* mobile originating */
+	switch (gh->msg_type) {
+	case GSM411_MT_CP_DATA:
+		/* 5.2.3.1.2: enter MO-wait for CP-ack */
+		/* 5.2.3.2.3: enter MT-wait for CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_WAIT_CP_ACK;
+		trans->sms.cp_timer.data = trans;
+		trans->sms.cp_timer.cb = cp_timer_expired;
+		/* 5.3.2.1: Set Timer TC1A */
+		bsc_schedule_timer(&trans->sms.cp_timer, GSM411_TMR_TC1A);
+		DEBUGP(DSMS, "TX: CP-DATA ");
+		break;
+	case GSM411_MT_CP_ACK:
+		DEBUGP(DSMS, "TX: CP-ACK ");
+		break;
+	case GSM411_MT_CP_ERROR:
+		DEBUGP(DSMS, "TX: CP-ERROR ");
+		break;
+	}
+
+	DEBUGPC(DSMS, "trans=%x\n", trans->transaction_id);
+
+	return gsm411_sendmsg(trans->conn, msg, trans->sms.link_id);
+}
+
+/* Prefix msg with a RP-DATA header and send as CP-DATA */
+static int gsm411_rp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     u_int8_t rp_msg_type, u_int8_t rp_msg_ref)
+{
+	struct gsm411_rp_hdr *rp;
+	u_int8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */
+
+	return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_DATA);
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+static u_int8_t bcdify(u_int8_t value)
+{
+	u_int8_t ret;
+
+	ret = value / 10;
+	ret |= (value % 10) << 4;
+
+	return ret;
+}
+
+/* Turn semi-octet representation into int: 0x89 => 98 */
+static u_int8_t unbcdify(u_int8_t value)
+{
+	u_int8_t ret;
+
+	if ((value & 0x0F) > 9 || (value >> 4) > 9)
+		LOGP(DSMS, LOGL_ERROR,
+		     "unbcdify got too big nibble: 0x%02X\n", value);
+
+	ret = (value&0x0F)*10;
+	ret += value>>4;
+
+	return ret;
+}
+
+/* Generate 03.40 TP-SCTS */
+static void gsm340_gen_scts(u_int8_t *scts, time_t time)
+{
+	struct tm *tm = localtime(&time);
+
+	*scts++ = bcdify(tm->tm_year % 100);
+	*scts++ = bcdify(tm->tm_mon + 1);
+	*scts++ = bcdify(tm->tm_mday);
+	*scts++ = bcdify(tm->tm_hour);
+	*scts++ = bcdify(tm->tm_min);
+	*scts++ = bcdify(tm->tm_sec);
+	*scts++ = bcdify(tm->tm_gmtoff/(60*15));
+}
+
+/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */
+static time_t gsm340_scts(u_int8_t *scts)
+{
+	struct tm tm;
+
+	u_int8_t yr = unbcdify(*scts++);
+
+	if (yr <= 80)
+		tm.tm_year = 100 + yr;
+	else
+		tm.tm_year = yr;
+	tm.tm_mon  = unbcdify(*scts++) - 1;
+	tm.tm_mday = unbcdify(*scts++);
+	tm.tm_hour = unbcdify(*scts++);
+	tm.tm_min  = unbcdify(*scts++);
+	tm.tm_sec  = unbcdify(*scts++);
+	/* according to gsm 03.40 time zone is
+	   "expressed in quarters of an hour" */
+	tm.tm_gmtoff = unbcdify(*scts++) * 15*60;
+
+	return mktime(&tm);
+}
+
+/* Return the default validity period in minutes */
+static unsigned long gsm340_vp_default(void)
+{
+	unsigned long minutes;
+	/* Default validity: two days */
+	minutes = 24 * 60 * 2;
+	return minutes;
+}
+
+/* Decode validity period format 'relative' */
+static unsigned long gsm340_vp_relative(u_int8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.1 */
+	u_int8_t vp;
+	unsigned long minutes;
+
+	vp = *(sms_vp);
+	if (vp <= 143)
+		minutes = vp + 1 * 5;
+	else if (vp <= 167)
+		minutes = 12*60 + (vp-143) * 30;
+	else if (vp <= 196)
+		minutes = vp-166 * 60 * 24;
+	else
+		minutes = vp-192 * 60 * 24 * 7;
+	return minutes;
+}
+
+/* Decode validity period format 'absolute' */
+static unsigned long gsm340_vp_absolute(u_int8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.2 */
+	time_t expires, now;
+	unsigned long minutes;
+
+	expires = gsm340_scts(sms_vp);
+	now = time(NULL);
+	if (expires <= now)
+		minutes = 0;
+	else
+		minutes = (expires-now)/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in integer representation' */
+static unsigned long gsm340_vp_relative_integer(u_int8_t *sms_vp)
+{
+	u_int8_t vp;
+	unsigned long minutes;
+	vp = *(sms_vp);
+	if (vp == 0) {
+		LOGP(DSMS, LOGL_ERROR,
+		     "reserved relative_integer validity period\n");
+		return gsm340_vp_default();
+	}
+	minutes = vp/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in semi-octet representation' */
+static unsigned long gsm340_vp_relative_semioctet(u_int8_t *sms_vp)
+{
+	unsigned long minutes;
+	minutes = unbcdify(*sms_vp++)*60;  /* hours */
+	minutes += unbcdify(*sms_vp++);    /* minutes */
+	minutes += unbcdify(*sms_vp++)/60; /* seconds */
+	return minutes;
+}
+
+/* decode validity period. return minutes */
+static unsigned long gsm340_validity_period(u_int8_t sms_vpf, u_int8_t *sms_vp)
+{
+	u_int8_t fi; /* functionality indicator */
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		return gsm340_vp_relative(sms_vp);
+	case GSM340_TP_VPF_ABSOLUTE:
+		return gsm340_vp_absolute(sms_vp);
+	case GSM340_TP_VPF_ENHANCED:
+		/* Chapter 9.2.3.12.3 */
+		fi = *sms_vp++;
+		/* ignore additional fi */
+		if (fi & (1<<7)) sms_vp++;
+		/* read validity period format */
+		switch (fi & 0x7) {
+		case 0x0:
+			return gsm340_vp_default(); /* no vpf specified */
+		case 0x1:
+			return gsm340_vp_relative(sms_vp);
+		case 0x2:
+			return gsm340_vp_relative_integer(sms_vp);
+		case 0x3:
+			return gsm340_vp_relative_semioctet(sms_vp);
+		default:
+			/* The GSM spec says that the SC should reject any
+			   unsupported and/or undefined values. FIXME */
+			LOGP(DSMS, LOGL_ERROR,
+			     "Reserved enhanced validity period format\n");
+			return gsm340_vp_default();
+		}
+	case GSM340_TP_VPF_NONE:
+	default:
+		return gsm340_vp_default();
+	}
+}
+
+/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */
+enum sms_alphabet gsm338_get_sms_alphabet(u_int8_t dcs)
+{
+	u_int8_t cgbits = dcs >> 4;
+	enum sms_alphabet alpha = DCS_NONE;
+
+	if ((cgbits & 0xc) == 0) {
+		if (cgbits & 2) {
+			LOGP(DSMS, LOGL_NOTICE,
+			     "Compressed SMS not supported yet\n");
+			return 0xffffffff;
+		}
+
+		switch ((dcs >> 2)&0x03) {
+		case 0:
+			alpha = DCS_7BIT_DEFAULT;
+			break;
+		case 1:
+			alpha = DCS_8BIT_DATA;
+			break;
+		case 2:
+			alpha = DCS_UCS2;
+			break;
+		}
+	} else if (cgbits == 0xc || cgbits == 0xd)
+		alpha = DCS_7BIT_DEFAULT;
+	else if (cgbits == 0xe)
+		alpha = DCS_UCS2;
+	else if (cgbits == 0xf) {
+		if (dcs & 4)
+			alpha = DCS_8BIT_DATA;
+		else
+			alpha = DCS_7BIT_DEFAULT;
+	}
+
+	return alpha;
+}
+
+static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms)
+{
+	if (db_sms_store(gsms) != 0) {
+		LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	/* dispatch a signal to tell higher level about it */
+	send_signal(S_SMS_SUBMITTED, NULL, gsms, 0);
+
+	return 0;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+static int gsm340_gen_oa(u_int8_t *oa, unsigned int oa_len,
+			 struct gsm_subscriber *subscr)
+{
+	int len_in_bytes;
+
+	oa[1] = 0xb9; /* networks-specific number, private numbering plan */
+
+	len_in_bytes = gsm48_encode_bcd_number(oa, oa_len, 1, subscr->extension);
+
+	/* GSM 03.40 tells us the length is in 'useful semi-octets' */
+	oa[0] = strlen(subscr->extension) & 0xff;
+
+	return len_in_bytes;
+}
+
+/* generate a msgb containing a TPDU derived from struct gsm_sms,
+ * returns total size of TPDU */
+static int gsm340_gen_tpdu(struct msgb *msg, struct gsm_sms *sms)
+{
+	u_int8_t *smsp;
+	u_int8_t oa[12];	/* max len per 03.40 */
+	u_int8_t oa_len = 0;
+	u_int8_t octet_len;
+	unsigned int old_msg_len = msg->len;
+
+	/* generate first octet with masked bits */
+	smsp = msgb_put(msg, 1);
+	/* TP-MTI (message type indicator) */
+	*smsp = GSM340_SMS_DELIVER_SC2MS;
+	/* TP-MMS (more messages to send) */
+	if (0 /* FIXME */)
+		*smsp |= 0x04;
+	/* TP-SRI(deliver)/SRR(submit) */
+	if (sms->status_rep_req)
+		*smsp |= 0x20;
+	/* TP-UDHI (indicating TP-UD contains a header) */
+	if (sms->ud_hdr_ind)
+		*smsp |= 0x40;
+	
+	/* generate originator address */
+	oa_len = gsm340_gen_oa(oa, sizeof(oa), sms->sender);
+	smsp = msgb_put(msg, oa_len);
+	memcpy(smsp, oa, oa_len);
+
+	/* generate TP-PID */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->protocol_id;
+
+	/* generate TP-DCS */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->data_coding_scheme;
+
+	/* generate TP-SCTS */
+	smsp = msgb_put(msg, 7);
+	gsm340_gen_scts(smsp, time(NULL));
+
+	/* generate TP-UDL */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->user_data_len;
+
+	/* generate TP-UD */
+	switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) {
+	case DCS_7BIT_DEFAULT:
+		octet_len = sms->user_data_len*7/8;
+		if (sms->user_data_len*7%8 != 0)
+			octet_len++;
+		/* Warning, user_data_len indicates the amount of septets
+		 * (characters), we need amount of octets occupied */
+		smsp = msgb_put(msg, octet_len);
+		memcpy(smsp, sms->user_data, octet_len);
+		break;
+	case DCS_UCS2:
+	case DCS_8BIT_DATA:
+		smsp = msgb_put(msg, sms->user_data_len);
+		memcpy(smsp, sms->user_data, sms->user_data_len);
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n",
+		     sms->data_coding_scheme);
+		break;
+	}
+
+	return msg->len - old_msg_len;
+}
+
+/* process an incoming TPDU (called from RP-DATA)
+ * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */
+static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	u_int8_t *smsp = msgb_sms(msg);
+	struct gsm_sms *gsms;
+	u_int8_t sms_mti, sms_mms, sms_vpf, sms_alphabet, sms_rp;
+	u_int8_t *sms_vp;
+	u_int8_t da_len_bytes;
+	u_int8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
+	int rc = 0;
+
+	counter_inc(conn->bts->network->stats.sms.submitted);
+
+	gsms = sms_alloc();
+	if (!gsms)
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+	/* invert those fields where 0 means active/present */
+	sms_mti = *smsp & 0x03;
+	sms_mms = !!(*smsp & 0x04);
+	sms_vpf = (*smsp & 0x18) >> 3;
+	gsms->status_rep_req = (*smsp & 0x20);
+	gsms->ud_hdr_ind = (*smsp & 0x40);
+	sms_rp  = (*smsp & 0x80);
+
+	smsp++;
+	gsms->msg_ref = *smsp++;
+
+	/* length in bytes of the destination address */
+	da_len_bytes = 2 + *smsp/2 + *smsp%2;
+	if (da_len_bytes > 12) {
+		LOGP(DSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
+		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+		goto out;
+	}
+	memset(address_lv, 0, sizeof(address_lv));
+	memcpy(address_lv, smsp, da_len_bytes);
+	/* mangle first byte to reflect length in bytes, not digits */
+	address_lv[0] = da_len_bytes - 1;
+	/* convert to real number */
+	gsm48_decode_bcd_number(gsms->dest_addr, sizeof(gsms->dest_addr), address_lv, 1);
+	smsp += da_len_bytes;
+
+	gsms->protocol_id = *smsp++;
+	gsms->data_coding_scheme = *smsp++;
+
+	sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme);
+	if (sms_alphabet == 0xffffffff) {
+		sms_free(gsms);
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		sms_vp = smsp++;
+		break;
+	case GSM340_TP_VPF_ABSOLUTE:
+	case GSM340_TP_VPF_ENHANCED:
+		sms_vp = smsp;
+		/* the additional functionality indicator... */
+		if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
+			smsp++;
+		smsp += 7;
+		break;
+	case GSM340_TP_VPF_NONE:
+		sms_vp = 0;
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE,
+		     "SMS Validity period not implemented: 0x%02x\n", sms_vpf);
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	gsms->user_data_len = *smsp++;
+	if (gsms->user_data_len) {
+		memcpy(gsms->user_data, smsp, gsms->user_data_len);
+
+		switch (sms_alphabet) {
+		case DCS_7BIT_DEFAULT:
+			gsm_7bit_decode(gsms->text, smsp, gsms->user_data_len);
+			break;
+		case DCS_8BIT_DATA:
+		case DCS_UCS2:
+		case DCS_NONE:
+			break;
+		}
+	}
+
+	gsms->sender = subscr_get(conn->subscr);
+
+	LOGP(DSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, "
+	     "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
+	     "UserDataLength: 0x%02x, UserData: \"%s\"\n",
+	     subscr_name(gsms->sender), sms_mti, sms_vpf, gsms->msg_ref,
+	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dest_addr,
+	     gsms->user_data_len,
+			sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
+				hexdump(gsms->user_data, gsms->user_data_len));
+
+	gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
+
+	/* FIXME: This looks very wrong */
+	send_signal(0, NULL, gsms, 0);
+
+	/* determine gsms->receiver based on dialled number */
+	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr);
+	if (!gsms->receiver) {
+		rc = 1; /* cause 1: unknown subscriber */
+		counter_inc(conn->bts->network->stats.sms.no_receiver);
+		goto out;
+	}
+
+	switch (sms_mti) {
+	case GSM340_SMS_SUBMIT_MS2SC:
+		/* MS is submitting a SMS */
+		rc = gsm340_rx_sms_submit(msg, gsms);
+		break;
+	case GSM340_SMS_COMMAND_MS2SC:
+	case GSM340_SMS_DELIVER_REP_MS2SC:
+		LOGP(DSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	}
+
+	if (!rc && !gsms->receiver)
+		rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+
+out:
+	sms_free(gsms);
+
+	return rc;
+}
+
+static int gsm411_send_rp_ack(struct gsm_trans *trans, u_int8_t msg_ref)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	DEBUGP(DSMS, "TX: SMS RP ACK\n");
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ACK_MT, msg_ref);
+}
+
+static int gsm411_send_rp_error(struct gsm_trans *trans,
+				u_int8_t msg_ref, u_int8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	msgb_tv_put(msg, 1, cause);
+
+	LOGP(DSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause,
+		get_value_string(rp_cause_strs, cause));
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ERROR_MT, msg_ref);
+}
+
+/* Receive a 04.11 TPDU inside RP-DATA / user data */
+static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans,
+			  struct gsm411_rp_hdr *rph,
+			  u_int8_t src_len, u_int8_t *src,
+			  u_int8_t dst_len, u_int8_t *dst,
+			  u_int8_t tpdu_len, u_int8_t *tpdu)
+{
+	int rc = 0;
+
+	if (src_len && src)
+		LOGP(DSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n");
+
+	if (!dst_len || !dst || !tpdu_len || !tpdu) {
+		LOGP(DSMS, LOGL_ERROR,
+			"RP-DATA (MO) without DST or TPDU ?!?\n");
+		gsm411_send_rp_error(trans, rph->msg_ref,
+				     GSM411_RP_CAUSE_INV_MAND_INF);
+		return -EIO;
+	}
+	msg->l4h = tpdu;
+
+	DEBUGP(DSMS, "DST(%u,%s)\n", dst_len, hexdump(dst, dst_len));
+
+	rc = gsm340_rx_tpdu(trans->conn, msg);
+	if (rc == 0)
+		return gsm411_send_rp_ack(trans, rph->msg_ref);
+	else if (rc > 0)
+		return gsm411_send_rp_error(trans, rph->msg_ref, rc);
+	else
+		return rc;
+}
+
+/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */
+static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	u_int8_t src_len, dst_len, rpud_len;
+	u_int8_t *src = NULL, *dst = NULL , *rp_ud = NULL;
+
+	/* in the MO case, this should always be zero length */
+	src_len = rph->data[0];
+	if (src_len)
+		src = &rph->data[1];
+
+	dst_len = rph->data[1+src_len];
+	if (dst_len)
+		dst = &rph->data[1+src_len+1];
+
+	rpud_len = rph->data[1+src_len+1+dst_len];
+	if (rpud_len)
+		rp_ud = &rph->data[1+src_len+1+dst_len+1];
+
+	DEBUGP(DSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n",
+		src_len, dst_len, rpud_len);
+	return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst,
+				rpud_len, rp_ud);
+}
+
+/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */
+static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans,
+			    struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms = trans->sms.sms;
+
+	/* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it
+	 * successfully received a SMS.  We can now safely mark it as
+	 * transmitted */
+
+	if (!trans->sms.is_mt) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ACK on a MO transfer ?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+	}
+
+	if (!sms) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+	}
+
+	/* mark this SMS as sent in database */
+	db_sms_mark_sent(sms);
+
+	send_signal(S_SMS_DELIVERED, trans, sms, 0);
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	/* check for more messages for this subscriber */
+	sms = db_sms_get_unsent_for_subscr(trans->subscr);
+	if (sms)
+		gsm411_send_sms(trans->conn, sms);
+	else
+		gsm411_release_conn(trans->conn);
+
+	/* free the transaction here */
+	trans_free(trans);
+	return 0;
+}
+
+static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans,
+			      struct gsm411_rp_hdr *rph)
+{
+	struct gsm_network *net = trans->conn->bts->network;
+	struct gsm_sms *sms = trans->sms.sms;
+	u_int8_t cause_len = rph->data[0];
+	u_int8_t cause = rph->data[1];
+
+	/* Error in response to MT RP_DATA, i.e. the MS did not
+	 * successfully receive the SMS.  We need to investigate
+	 * the cause and take action depending on it */
+
+	LOGP(DSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
+	     subscr_name(trans->conn->subscr), cause_len, cause,
+	     get_value_string(rp_cause_strs, cause));
+
+	if (!trans->sms.is_mt) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ERR on a MO transfer ?\n");
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+#endif
+	}
+
+	if (!sms) {
+		LOGP(DSMS, LOGL_ERROR,
+			"RX RP-ERR, but no sms in transaction?!?\n");
+		return -EINVAL;
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+#endif
+	}
+
+	if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) {
+		/* MS has not enough memory to store the message.  We need
+		 * to store this in our database and wait for a SMMA message */
+		/* FIXME */
+		send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0);
+		counter_inc(net->stats.sms.rp_err_mem);
+	} else {
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		counter_inc(net->stats.sms.rp_err_other);
+	}
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	return 0;
+}
+
+static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms;
+	int rc;
+
+	rc = gsm411_send_rp_ack(trans, rph->msg_ref);
+	trans->sms.rp_state = GSM411_RPS_IDLE;
+
+	/* MS tells us that it has memory for more SMS, we need
+	 * to check if we have any pending messages for it and then
+	 * transfer those */
+	send_signal(S_SMS_SMMA, trans, NULL, 0);
+
+	/* check for more messages for this subscriber */
+	sms = db_sms_get_unsent_for_subscr(trans->subscr);
+	if (sms)
+		gsm411_send_sms(trans->conn, sms);
+	else
+		gsm411_release_conn(trans->conn);
+
+	return rc;
+}
+
+static int gsm411_rx_cp_data(struct msgb *msg, struct gsm48_hdr *gh,
+			     struct gsm_trans *trans)
+{
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	u_int8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MT_RP_DATA_MO:
+		DEBUGP(DSMS, "RX SMS RP-DATA (MO)\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK;
+		rc = gsm411_rx_rp_data(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ACK_MO:
+		DEBUGP(DSMS,"RX SMS RP-ACK (MO)\n");
+		rc = gsm411_rx_rp_ack(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_SMMA_MO:
+		DEBUGP(DSMS, "RX SMS RP-SMMA\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK;
+		rc = gsm411_rx_rp_smma(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ERROR_MO:
+		rc = gsm411_rx_rp_error(msg, trans, rp_data);
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		rc = gsm411_send_rp_error(trans, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSGTYPE_NOTEXIST);
+		break;
+	}
+
+	return rc;
+}
+
+/* send CP-ACK to given transaction */
+static int gsm411_tx_cp_ack(struct gsm_trans *trans)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	int rc;
+
+	rc = gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ACK);
+
+	if (trans->sms.is_mt) {
+		/* If this is a MT SMS DELIVER, we can clear transaction here */
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		//trans_free(trans);
+	}
+
+	return rc;
+}
+
+static int gsm411_tx_cp_error(struct gsm_trans *trans, u_int8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	u_int8_t *causep;
+
+	LOGP(DSMS, LOGL_NOTICE, "TX CP-ERROR, cause %d (%s)\n", cause,
+		get_value_string(cp_cause_strs, cause));
+
+	causep = msgb_put(msg, 1);
+	*causep = cause;
+
+	return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ERROR);
+}
+
+/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
+int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn,
+		    struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type;
+	u_int8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */
+	struct gsm_trans *trans;
+	int rc = 0;
+
+	if (!conn->subscr)
+		return -EIO;
+		/* FIXME: send some error message */
+
+	DEBUGP(DSMS, "trans_id=%x ", transaction_id);
+	trans = trans_find_by_id(conn->subscr, GSM48_PDISC_SMS,
+				 transaction_id);
+	if (!trans) {
+		DEBUGPC(DSMS, "(new) ");
+		trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGPC(DSMS, "No memory for trans\n");
+			/* FIXME: send some error message */
+			return -ENOMEM;
+		}
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans->sms.rp_state = GSM411_RPS_IDLE;
+		trans->sms.is_mt = 0;
+		trans->sms.link_id = UM_SAPI_SMS;
+
+		trans->conn = conn;
+	}
+
+	switch(msg_type) {
+	case GSM411_MT_CP_DATA:
+		DEBUGPC(DSMS, "RX SMS CP-DATA\n");
+
+		/* 5.4: For MO, if a CP-DATA is received for a new
+		 * transaction, equals reception of an implicit
+		 * last CP-ACK for previous transaction */
+		if (trans->sms.cp_state == GSM411_CPS_IDLE) {
+			int i;
+			struct gsm_trans *ptrans;
+
+			/* Scan through all remote initiated transactions */
+			for (i=8; i<15; i++) {
+				if (i == transaction_id)
+					continue;
+
+				ptrans = trans_find_by_id(conn->subscr,
+				                          GSM48_PDISC_SMS, i);
+				if (!ptrans)
+					continue;
+
+				DEBUGP(DSMS, "Implicit CP-ACK for trans_id=%x\n", i);
+
+				/* Finish it for good */
+				bsc_del_timer(&ptrans->sms.cp_timer);
+				ptrans->sms.cp_state = GSM411_CPS_IDLE;
+				trans_free(ptrans);
+			}
+		}
+
+		/* 5.2.3.1.3: MO state exists when SMC has received
+		 * CP-DATA, including sending of the assoc. CP-ACK */
+		/* 5.2.3.2.4: MT state exists when SMC has received
+		 * CP-DATA, including sending of the assoc. CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED;
+
+		/* SMC instance acknowledges the CP-DATA frame */
+		gsm411_tx_cp_ack(trans);
+		
+		rc = gsm411_rx_cp_data(msg, gh, trans);
+#if 0
+		/* Send CP-ACK or CP-ERORR in response */
+		if (rc < 0) {
+			rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_NET_FAIL);
+		} else
+			rc = gsm411_tx_cp_ack(trans);
+#endif
+		break;
+	case GSM411_MT_CP_ACK:
+		/* previous CP-DATA in this transaction was confirmed */
+		DEBUGPC(DSMS, "RX SMS CP-ACK\n");
+		/* 5.2.3.1.3: MO state exists when SMC has received CP-ACK */
+		/* 5.2.3.2.4: MT state exists when SMC has received CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED;
+		/* Stop TC1* after CP-ACK has been received */
+		bsc_del_timer(&trans->sms.cp_timer);
+
+		if (!trans->sms.is_mt) {
+			/* FIXME: we have sent one CP-DATA, which was now
+			 * acknowledged.  Check if we want to transfer more,
+			 * i.e. multi-part message */
+			trans->sms.cp_state = GSM411_CPS_IDLE;
+			trans_free(trans);
+		}
+		break;
+	case GSM411_MT_CP_ERROR:
+		DEBUGPC(DSMS, "RX SMS CP-ERROR, cause %d (%s)\n", gh->data[0],
+			get_value_string(cp_cause_strs, gh->data[0]));
+		bsc_del_timer(&trans->sms.cp_timer);
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans_free(trans);
+		break;
+	default:
+		DEBUGPC(DSMS, "RX Unimplemented CP msg_type: 0x%02x\n", msg_type);
+		rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_MSGTYPE_NOTEXIST);
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans_free(trans);
+		break;
+	}
+
+	return rc;
+}
+
+/* Take a SMS in gsm_sms structure and send it through an already
+ * existing lchan. We also assume that the caller ensured this lchan already
+ * has a SAPI3 RLL connection! */
+int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	struct gsm_trans *trans;
+	u_int8_t *data, *rp_ud_len;
+	u_int8_t msg_ref = 42;
+	int transaction_id;
+	int rc;
+
+	transaction_id = trans_assign_trans_id(conn->subscr, GSM48_PDISC_SMS, 0);
+	if (transaction_id == -1) {
+		LOGP(DSMS, LOGL_ERROR, "No available transaction ids\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+		sms_free(sms);
+		return -EBUSY;
+	}
+
+	DEBUGP(DSMS, "send_sms_lchan()\n");
+
+	/* FIXME: allocate transaction with message reference */
+	trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
+			    transaction_id, new_callref++);
+	if (!trans) {
+		LOGP(DSMS, LOGL_ERROR, "No memory for trans\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+		sms_free(sms);
+		/* FIXME: send some error message */
+		return -ENOMEM;
+	}
+	trans->sms.cp_state = GSM411_CPS_IDLE;
+	trans->sms.rp_state = GSM411_RPS_IDLE;
+	trans->sms.is_mt = 1;
+	trans->sms.sms = sms;
+	trans->sms.link_id = UM_SAPI_SMS;	/* FIXME: main or SACCH ? */
+
+	trans->conn = conn;
+
+	/* Hardcode SMSC Originating Address for now */
+	data = (u_int8_t *)msgb_put(msg, 8);
+	data[0] = 0x07;	/* originator length == 7 */
+	data[1] = 0x91; /* type of number: international, ISDN */
+	data[2] = 0x44; /* 447785016005 */
+	data[3] = 0x77;
+	data[4] = 0x58;
+	data[5] = 0x10;
+	data[6] = 0x06;
+	data[7] = 0x50;
+
+	/* Hardcoded Destination Address */
+	data = (u_int8_t *)msgb_put(msg, 1);
+	data[0] = 0;	/* destination length == 0 */
+
+	/* obtain a pointer for the rp_ud_len, so we can fill it later */
+	rp_ud_len = (u_int8_t *)msgb_put(msg, 1);
+
+	/* generate the 03.40 TPDU */
+	rc = gsm340_gen_tpdu(msg, sms);
+	if (rc < 0) {
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		trans_free(trans);
+		sms_free(sms);
+		msgb_free(msg);
+		return rc;
+	}
+
+	*rp_ud_len = rc;
+
+	DEBUGP(DSMS, "TX: SMS DELIVER\n");
+
+	counter_inc(conn->bts->network->stats.sms.delivered);
+	db_sms_inc_deliver_attempts(trans->sms.sms);
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_DATA_MT, msg_ref);
+	/* FIXME: enter 'wait for RP-ACK' state, start TR1N */
+}
+
+/* paging callback. Here we get called if paging a subscriber has
+ * succeeded or failed. */
+static int paging_cb_send_sms(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_conn, void *_sms)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct gsm_sms *sms = _sms;
+	int rc = 0;
+
+	DEBUGP(DSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p,"
+		"conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id);
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		gsm411_send_sms(conn, sms);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_OOM:
+	case GSM_PAGING_BUSY:
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event);
+		sms_free(sms);
+		rc = -ETIMEDOUT;
+		break;
+	default:
+		LOGP(DSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event);
+	}
+
+	return rc;
+}
+
+/* high-level function to send a SMS to a given subscriber. The function
+ * will take care of paging the subscriber, establishing the RLL SAPI3
+ * connection, etc. */
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms)
+{
+	struct gsm_subscriber_connection *conn;
+
+	/* check if we already have an open lchan to the subscriber.
+	 * if yes, send the SMS this way */
+	conn = connection_for_subscr(subscr);
+	if (conn) {
+		return gsm411_send_sms(conn, sms);
+	}
+
+	/* if not, we have to start paging */
+	subscr_get_channel(subscr, RSL_CHANNEED_SDCCH, paging_cb_send_sms, sms);
+	return 0;
+}
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans)
+{
+	if (trans->sms.sms) {
+		LOGP(DSMS, LOGL_ERROR, "Transaction contains SMS.\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0);
+		sms_free(trans->sms.sms);
+		trans->sms.sms = NULL;
+	}
+
+	bsc_del_timer(&trans->sms.cp_timer);
+}
+
+void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_network *net;
+	struct gsm_trans *trans, *tmp;
+
+	subscr = subscr_get(conn->subscr);
+	net = conn->bts->network;
+
+	llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry)
+		if (trans->conn == conn) {
+			struct gsm_sms *sms = trans->sms.sms;
+			if (!sms) {
+				LOGP(DSMS, LOGL_ERROR, "SAPI Reject but no SMS.\n");
+				continue;
+			}
+
+			send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+			sms_free(sms);
+			trans->sms.sms = NULL;
+			trans_free(trans);
+		}
+
+	subscr_put_channel(subscr);
+	subscr_put(subscr);
+}
+
diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c
new file mode 100644
index 0000000..494c319
--- /dev/null
+++ b/src/libmsc/gsm_04_80.c
@@ -0,0 +1,175 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/bsc_api.h>
+
+#include <osmocore/gsm0480.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+
+static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, u_int8_t tag)
+{
+	uint8_t *data = msgb_push(msgb, 2);
+
+	data[0] = tag;
+	data[1] = msgb->len - 2;
+	return data;
+}
+
+static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, u_int8_t tag,
+					    u_int8_t value)
+{
+	uint8_t *data = msgb_push(msgb, 3);
+
+	data[0] = tag;
+	data[1] = 1;
+	data[2] = value;
+	return data;
+}
+
+
+/* Send response to a mobile-originated ProcessUnstructuredSS-Request */
+int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
+			       const struct msgb *in_msg, const char *response_text,
+			       const struct ussd_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	u_int8_t *ptr8;
+	int response_len;
+
+	/* First put the payload text into the message */
+	ptr8 = msgb_put(msg, 0);
+	response_len = gsm_7bit_encode(ptr8, response_text);
+	msgb_put(msg, response_len);
+
+	/* Then wrap it as an Octet String */
+	msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG);
+
+	/* Pre-pend the DCS octet string */
+	msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
+
+	/* Then wrap these as a Sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the operation code */
+	msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
+			GSM0480_OP_CODE_PROCESS_USS_REQ);
+
+	/* Wrap the operation code and IA5 string as a sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Return Result component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id
+					| (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
+			     const struct msgb *in_msg,
+			     const struct ussd_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	/* First insert the problem code */
+	msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL,
+			GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
+
+	/* Before it insert the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Reject component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->proto_discr |= req->transaction_id | (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm0480_create_unstructuredSS_Notify(level, text);
+	if (!msg)
+		return -1;
+
+	gsm0480_wrap_invoke(msg, GSM0480_OP_CODE_USS_NOTIFY, 0);
+	gsm0480_wrap_facility(msg);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->msg_type = GSM0480_MTYPE_REGISTER;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm48_msgb_alloc();
+	if (!msg)
+		return -1;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c
new file mode 100644
index 0000000..db61f25
--- /dev/null
+++ b/src/libmsc/gsm_subscriber.c
@@ -0,0 +1,410 @@
+/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocore/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+
+void *tall_sub_req_ctx;
+
+extern struct llist_head *subscr_bsc_active_subscriber(void);
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+                         gsm_cbfn *cb, void *cb_data);
+
+
+/*
+ * Struct for pending channel requests. This is managed in the
+ * llist_head requests of each subscriber. The reference counting
+ * should work in such a way that a subscriber with a pending request
+ * remains in memory.
+ */
+struct subscr_request {
+	struct llist_head entry;
+
+	/* back reference */
+	struct gsm_subscriber *subscr;
+
+	/* the requested channel type */
+	int channel_type;
+
+	/* what did we do */
+	int state;
+
+	/* the callback data */
+	gsm_cbfn *cbfn;
+	void *param;
+};
+
+enum {
+	REQ_STATE_INITIAL,
+	REQ_STATE_QUEUED,
+	REQ_STATE_PAGED,
+	REQ_STATE_FAILED_START,
+	REQ_STATE_DISPATCHED,
+};
+
+/*
+ * We got the channel assigned and can now hand this channel
+ * over to one of our callbacks.
+ */
+static int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
+                                  struct msgb *msg, void *data, void *param)
+{
+	struct subscr_request *request;
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm_subscriber *subscr = param;
+	struct paging_signal_data sig_data;
+
+	/* There is no request anymore... */
+	if (llist_empty(&subscr->requests))
+		return -1;
+
+	/* Dispatch signal */
+	sig_data.subscr = subscr;
+	sig_data.bts	= conn ? conn->bts : NULL;
+	sig_data.conn	= conn;
+	sig_data.paging_result = event;
+	dispatch_signal(
+		SS_PAGING,
+		event == GSM_PAGING_SUCCEEDED ?
+			S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
+		&sig_data
+	);
+
+	/*
+	 * FIXME: What to do with paging requests coming during
+	 * this callback? We must be sure to not start paging when
+	 * we have an active connection to a subscriber and to make
+	 * the subscr_put_channel work as required...
+	 */
+	request = (struct subscr_request *)subscr->requests.next;
+	request->state = REQ_STATE_DISPATCHED;
+	llist_del(&request->entry);
+	subscr->in_callback = 1;
+	request->cbfn(hooknum, event, msg, data, request->param);
+	subscr->in_callback = 0;
+
+	if (event != GSM_PAGING_SUCCEEDED) {
+		/*
+		 *  This is a workaround for a bigger issue. We have
+		 *  issued paging that might involve multiple BTSes
+		 *  and one of them have failed now. We will stop the
+		 *  other paging requests as well as the next timeout
+		 *  would work on the next paging request and the queue
+		 *  will do bad things. This should be fixed by counting
+		 *  the outstanding results.
+		 */
+		paging_request_stop(NULL, subscr, NULL, NULL);
+		subscr_put_channel(subscr);
+	}
+
+	subscr_put(subscr);
+	talloc_free(request);
+	return 0;
+}
+
+static int subscr_paging_sec_cb(unsigned int hooknum, unsigned int event,
+                                struct msgb *msg, void *data, void *param)
+{
+	int rc;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			/* Dispatch as paging failure */
+			rc = subscr_paging_dispatch(
+				GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED,
+				msg, data, param);
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_SUCCEEDED:
+			/* Dispatch as paging failure */
+			rc = subscr_paging_dispatch(
+				GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+				msg, data, param);
+			break;
+
+		default:
+			rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static int subscr_paging_cb(unsigned int hooknum, unsigned int event,
+                            struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm48_hdr *gh;
+	struct gsm48_pag_resp *pr;
+
+	/* Other cases mean problem, dispatch direclty */
+	if (event != GSM_PAGING_SUCCEEDED)
+		return subscr_paging_dispatch(hooknum, event, msg, data, param);
+
+	/* Get paging response */
+	gh = msgb_l3(msg);
+	pr = (struct gsm48_pag_resp *)gh->data;
+
+	/* We _really_ have a channel, secure it now ! */
+	return gsm48_secure_channel(conn, pr->key_seq, subscr_paging_sec_cb, param);
+}
+
+
+static void subscr_send_paging_request(struct gsm_subscriber *subscr)
+{
+	struct subscr_request *request;
+	int rc;
+
+	assert(!llist_empty(&subscr->requests));
+
+	request = (struct subscr_request *)subscr->requests.next;
+	request->state = REQ_STATE_PAGED;
+	rc = paging_request(subscr->net, subscr, request->channel_type,
+			    subscr_paging_cb, subscr);
+
+	/* paging failed, quit now */
+	if (rc <= 0) {
+		request->state = REQ_STATE_FAILED_START;
+		subscr_paging_cb(GSM_HOOK_RR_PAGING, GSM_PAGING_BUSY,
+				 NULL, NULL, subscr);
+	}
+}
+
+void subscr_get_channel(struct gsm_subscriber *subscr,
+			int type, gsm_cbfn *cbfn, void *param)
+{
+	struct subscr_request *request;
+
+	request = talloc(tall_sub_req_ctx, struct subscr_request);
+	if (!request) {
+		if (cbfn)
+			cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_OOM,
+				NULL, NULL, param);
+		return;
+	}
+
+	memset(request, 0, sizeof(*request));
+	request->subscr = subscr_get(subscr);
+	request->channel_type = type;
+	request->cbfn = cbfn;
+	request->param = param;
+	request->state = REQ_STATE_INITIAL;
+
+	/*
+	 * FIXME: We might be able to assign more than one
+	 * channel, e.g. voice and SMS submit at the same
+	 * time.
+	 */
+	if (!subscr->in_callback && llist_empty(&subscr->requests)) {
+		/* add to the list, send a request */
+		llist_add_tail(&request->entry, &subscr->requests);
+		subscr_send_paging_request(subscr);
+	} else {
+		/* this will be picked up later, from subscr_put_channel */
+		llist_add_tail(&request->entry, &subscr->requests);
+		request->state = REQ_STATE_QUEUED;
+	}
+}
+
+void subscr_put_channel(struct gsm_subscriber *subscr)
+{
+	/*
+	 * FIXME: Continue with other requests now... by checking
+	 * the gsm_subscriber inside the gsm_lchan. Drop the ref count
+	 * of the lchan after having asked the next requestee to handle
+	 * the channel.
+	 */
+	/*
+	 * FIXME: is the lchan is of a different type we could still
+	 * issue an immediate assignment for another channel and then
+	 * close this one.
+	 */
+	/*
+	 * Currently we will drop the last ref of the lchan which
+	 * will result in a channel release on RSL and we will start
+	 * the paging. This should work most of the time as the MS
+	 * will listen to the paging requests before we timeout
+	 */
+
+	if (subscr && !llist_empty(&subscr->requests))
+		subscr_send_paging_request(subscr);
+}
+
+
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net,
+					  u_int32_t tmsi)
+{
+	char tmsi_string[14];
+	struct gsm_subscriber *subscr;
+
+	/* we might have a record in memory already */
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (tmsi == subscr->tmsi)
+			return subscr_get(subscr);
+	}
+
+	sprintf(tmsi_string, "%u", tmsi);
+	return db_get_subscriber(net, GSM_SUBSCRIBER_TMSI, tmsi_string);
+}
+
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net,
+					  const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi);
+}
+
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net,
+					       const char *ext)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->extension, ext) == 0)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_EXTENSION, ext);
+}
+
+struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net,
+					unsigned long long id)
+{
+	struct gsm_subscriber *subscr;
+	char buf[32];
+	sprintf(buf, "%llu", id);
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->id == id)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_ID, buf);
+}
+
+
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason)
+{
+	int rc;
+
+	/* FIXME: Migrate pending requests from one BSC to another */
+	switch (reason) {
+	case GSM_SUBSCRIBER_UPDATE_ATTACHED:
+		s->net = bts->network;
+		/* Indicate "attached to LAC" */
+		s->lac = bts->location_area_code;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n",
+			subscr_name(s), s->lac);
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		dispatch_signal(SS_SUBSCR, S_SUBSCR_ATTACHED, s);
+		break;
+	case GSM_SUBSCRIBER_UPDATE_DETACHED:
+		/* Only detach if we are currently in this area */
+		if (bts->location_area_code == s->lac)
+			s->lac = GSM_LAC_RESERVED_DETACHED;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s));
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		dispatch_signal(SS_SUBSCR, S_SUBSCR_DETACHED, s);
+		break;
+	default:
+		fprintf(stderr, "subscr_update with unknown reason: %d\n",
+			reason);
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		break;
+	};
+
+	return rc;
+}
+
+void subscr_update_from_db(struct gsm_subscriber *sub)
+{
+	db_subscriber_update(sub);
+}
+
+int subscr_pending_requests(struct gsm_subscriber *sub)
+{
+	struct subscr_request *req;
+	int pending = 0;
+
+	llist_for_each_entry(req, &sub->requests, entry)
+		pending += 1;
+
+	return pending;
+}
+
+int subscr_pending_clear(struct gsm_subscriber *sub)
+{
+	int deleted = 0;
+	struct subscr_request *req, *tmp;
+
+	llist_for_each_entry_safe(req, tmp, &sub->requests, entry) {
+		subscr_put(req->subscr);
+		llist_del(&req->entry);
+		talloc_free(req);
+		deleted += 1;
+	}
+
+	return deleted;
+}
+
+int subscr_pending_dump(struct gsm_subscriber *sub, struct vty *vty)
+{
+	struct subscr_request *req;
+
+	vty_out(vty, "Pending Requests for Subscriber %llu.%s", sub->id, VTY_NEWLINE);
+	llist_for_each_entry(req, &sub->requests, entry) {
+		vty_out(vty, "Channel type: %d State: %d Sub: %llu.%s",
+			req->channel_type, req->state, req->subscr->id, VTY_NEWLINE);
+	}
+
+	return 0;
+}
+
+int subscr_pending_kick(struct gsm_subscriber *sub)
+{
+	subscr_put_channel(sub);
+	return 0;
+}
diff --git a/src/libmsc/mncc.c b/src/libmsc/mncc.c
new file mode 100644
index 0000000..3630b91
--- /dev/null
+++ b/src/libmsc/mncc.c
@@ -0,0 +1,110 @@
+/* mncc.c - utility routines for the MNCC API between the 04.08
+ *	    message parsing and the actual Call Control logic */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+static struct mncc_names {
+	char *name;
+	int value;
+} mncc_names[] = {
+	{"MNCC_SETUP_REQ",	0x0101},
+	{"MNCC_SETUP_IND",	0x0102},
+	{"MNCC_SETUP_RSP",	0x0103},
+	{"MNCC_SETUP_CNF",	0x0104},
+	{"MNCC_SETUP_COMPL_REQ",0x0105},
+	{"MNCC_SETUP_COMPL_IND",0x0106},
+	{"MNCC_CALL_CONF_IND",	0x0107},
+	{"MNCC_CALL_PROC_REQ",	0x0108},
+	{"MNCC_PROGRESS_REQ",	0x0109},
+	{"MNCC_ALERT_REQ",	0x010a},
+	{"MNCC_ALERT_IND",	0x010b},
+	{"MNCC_NOTIFY_REQ",	0x010c},
+	{"MNCC_NOTIFY_IND",	0x010d},
+	{"MNCC_DISC_REQ",	0x010e},
+	{"MNCC_DISC_IND",	0x010f},
+	{"MNCC_REL_REQ",	0x0110},
+	{"MNCC_REL_IND",	0x0111},
+	{"MNCC_REL_CNF",	0x0112},
+	{"MNCC_FACILITY_REQ",	0x0113},
+	{"MNCC_FACILITY_IND",	0x0114},
+	{"MNCC_START_DTMF_IND",	0x0115},
+	{"MNCC_START_DTMF_RSP",	0x0116},
+	{"MNCC_START_DTMF_REJ",	0x0117},
+	{"MNCC_STOP_DTMF_IND",	0x0118},
+	{"MNCC_STOP_DTMF_RSP",	0x0119},
+	{"MNCC_MODIFY_REQ",	0x011a},
+	{"MNCC_MODIFY_IND",	0x011b},
+	{"MNCC_MODIFY_RSP",	0x011c},
+	{"MNCC_MODIFY_CNF",	0x011d},
+	{"MNCC_MODIFY_REJ",	0x011e},
+	{"MNCC_HOLD_IND",	0x011f},
+	{"MNCC_HOLD_CNF",	0x0120},
+	{"MNCC_HOLD_REJ",	0x0121},
+	{"MNCC_RETRIEVE_IND",	0x0122},
+	{"MNCC_RETRIEVE_CNF",	0x0123},
+	{"MNCC_RETRIEVE_REJ",	0x0124},
+	{"MNCC_USERINFO_REQ",	0x0125},
+	{"MNCC_USERINFO_IND",	0x0126},
+	{"MNCC_REJ_REQ",	0x0127},
+	{"MNCC_REJ_IND",	0x0128},
+
+	{"MNCC_BRIDGE",		0x0200},
+	{"MNCC_FRAME_RECV",	0x0201},
+	{"MNCC_FRAME_DROP",	0x0202},
+	{"MNCC_LCHAN_MODIFY",	0x0203},
+
+	{"GSM_TCH_FRAME",	0x0300},
+
+	{NULL, 0} };
+
+char *get_mncc_name(int value)
+{
+	int i;
+
+	for (i = 0; mncc_names[i].name; i++) {
+		if (mncc_names[i].value == value)
+			return mncc_names[i].name;
+	}
+
+	return "MNCC_Unknown";
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+	data->fields |= MNCC_F_CAUSE;
+	data->cause.location = loc;
+	data->cause.value = val;
+}
+
diff --git a/src/libmsc/mncc_builtin.c b/src/libmsc/mncc_builtin.c
new file mode 100644
index 0000000..0226b27
--- /dev/null
+++ b/src/libmsc/mncc_builtin.c
@@ -0,0 +1,411 @@
+/* mncc_builtin.c - default, minimal built-in MNCC Application for
+ *		    standalone bsc_hack (netowrk-in-the-box mode) */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+void *tall_call_ctx;
+
+static LLIST_HEAD(call_list);
+
+static u_int32_t new_callref = 0x00000001;
+
+static void free_call(struct gsm_call *call)
+{
+	llist_del(&call->entry);
+	DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
+	talloc_free(call);
+}
+
+
+static struct gsm_call *get_call_ref(u_int32_t callref)
+{
+	struct gsm_call *callt;
+
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref)
+			return callt;
+	}
+	return NULL;
+}
+
+
+/* on incoming call, look up database and send setup to remote subscr. */
+static int mncc_setup_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *setup)
+{
+	struct gsm_mncc mncc;
+	struct gsm_call *remote;
+
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+
+	/* already have remote call */
+	if (call->remote_ref)
+		return 0;
+	
+	/* transfer mode 1 would be packet mode, which was never specified */
+	if (setup->bearer_cap.mode != 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support "
+			"packet mode\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* we currently only do speech */
+	if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support "
+			"voice calls\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* create remote call */
+	if (!(remote = talloc(tall_call_ctx, struct gsm_call))) {
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		goto out_reject;
+	}
+	llist_add_tail(&remote->entry, &call_list);
+	remote->net = call->net;
+	remote->callref = new_callref++;
+	DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n",
+		call->callref, remote->callref);
+
+	/* link remote call */
+	call->remote_ref = remote->callref;
+	remote->remote_ref = call->callref;
+
+	/* modify mode */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	mncc.lchan_mode = GSM48_CMODE_SPEECH_EFR;
+	DEBUGP(DMNCC, "(call %x) Modify channel mode.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, &mncc);
+
+	/* send call proceeding */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_CALL_PROC_REQ, &mncc);
+
+	/* send setup to remote */
+//	setup->fields |= MNCC_F_SIGNAL;
+//	setup->signal = GSM48_SIGNAL_DIALTONE;
+	setup->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_SETUP_REQ, setup);
+
+out_reject:
+	mncc_tx_to_cc(call->net, MNCC_REJ_REQ, &mncc);
+	free_call(call);
+	return 0;
+}
+
+static int mncc_alert_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *alert)
+{
+	struct gsm_call *remote;
+
+	/* send alerting to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	alert->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_ALERT_REQ, alert);
+}
+
+static int mncc_notify_ind(struct gsm_call *call, int msg_type,
+			   struct gsm_mncc *notify)
+{
+	struct gsm_call *remote;
+
+	/* send notify to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	notify->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_NOTIFY_REQ, notify);
+}
+
+static int mncc_setup_cnf(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *connect)
+{
+	struct gsm_mncc connect_ack, frame_recv;
+	struct gsm_network *net = call->net;
+	struct gsm_call *remote;
+	u_int32_t refs[2];
+
+	/* acknowledge connect */
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack);
+
+	/* send connect message to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	connect->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref);
+	mncc_tx_to_cc(remote->net, MNCC_SETUP_RSP, connect);
+
+	/* bridge tch */
+	refs[0] = call->callref;
+	refs[1] = call->remote_ref;
+	DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref);
+
+	/* in direct mode, we always have to bridge the channels */
+	if (ipacc_rtp_direct)
+		return mncc_tx_to_cc(call->net, MNCC_BRIDGE, refs);
+
+	/* proxy mode */
+	if (!net->handover.active) {
+		/* in the no-handover case, we can bridge, i.e. use
+		 * the old RTP proxy code */
+		return mncc_tx_to_cc(call->net, MNCC_BRIDGE, refs);
+	} else {
+		/* in case of handover, we need to re-write the RTP
+		 * SSRC, sequence and timestamp values and thus
+		 * need to enable RTP receive for both directions */
+		memset(&frame_recv, 0, sizeof(struct gsm_mncc));
+		frame_recv.callref = call->callref;
+		mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+		frame_recv.callref = call->remote_ref;
+		return mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+	}
+}
+
+static int mncc_disc_ind(struct gsm_call *call, int msg_type,
+			 struct gsm_mncc *disc)
+{
+	struct gsm_call *remote;
+
+	/* send release */
+	DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n",
+		call->callref, disc->cause.value);
+	mncc_tx_to_cc(call->net, MNCC_REL_REQ, disc);
+
+	/* send disc to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		return 0;
+	}
+	disc->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n",
+		remote->callref, disc->cause.value);
+	return mncc_tx_to_cc(remote->net, MNCC_DISC_REQ, disc);
+}
+
+static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	struct gsm_call *remote;
+
+	/* send release to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		free_call(call);
+		return 0;
+	}
+
+	rel->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n",
+		call->callref, rel->cause.value);
+
+	/*
+	 * Release this side of the call right now. Otherwise we end up
+	 * in this method for the other call and will also try to release
+	 * it and then we will end up with a double free and a crash
+	 */
+	free_call(call);
+	mncc_tx_to_cc(remote->net, MNCC_REL_REQ, rel);
+
+	return 0;
+}
+
+static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	free_call(call);
+	return 0;
+}
+
+/* receiving a TCH/F frame from the BSC code */
+static int mncc_rcv_tchf(struct gsm_call *call, int msg_type,
+			 struct gsm_data_frame *dfr)
+{
+	struct gsm_trans *remote_trans;
+
+	remote_trans = trans_find_by_callref(call->net, call->remote_ref);
+
+	/* this shouldn't really happen */
+	if (!remote_trans || !remote_trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n");
+		return -EIO;
+	}
+
+	/* RTP socket of remote end has meanwhile died */
+	if (!remote_trans->conn->lchan->abis_ip.rtp_socket)
+		return -EIO;
+
+	return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr);
+}
+
+
+/* Internal MNCC handler input function (from CC -> MNCC -> here) */
+int int_mncc_recv(struct gsm_network *net, struct msgb *msg)
+{
+	void *arg = msgb_data(msg);
+	struct gsm_mncc *data = arg;
+	int msg_type = data->msg_type;
+	int callref;
+	struct gsm_call *call = NULL, *callt;
+	int rc = 0;
+
+	/* Special messages */
+	switch(msg_type) {
+	}
+	
+	/* find callref */
+	callref = data->callref;
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref) {
+			call = callt;
+			break;
+		}
+	}
+
+	/* create callref, if setup is received */
+	if (!call) {
+		if (msg_type != MNCC_SETUP_IND)
+			goto out_free; /* drop */
+		/* create call */
+		if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
+			struct gsm_mncc rel;
+			
+			memset(&rel, 0, sizeof(struct gsm_mncc));
+			rel.callref = callref;
+			mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				       GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			mncc_tx_to_cc(net, MNCC_REL_REQ, &rel);
+			goto out_free;
+		}
+		llist_add_tail(&call->entry, &call_list);
+		call->net = net;
+		call->callref = callref;
+		DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref);
+	}
+
+	switch (msg_type) {
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+		break;
+	default:
+		DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref,
+			get_mncc_name(msg_type));
+		break;
+	}
+
+	switch(msg_type) {
+	case MNCC_SETUP_IND:
+		rc = mncc_setup_ind(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_CNF:
+		rc = mncc_setup_cnf(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_COMPL_IND:
+		break;
+	case MNCC_CALL_CONF_IND:
+		/* we now need to MODIFY the channel */
+		data->lchan_mode = GSM48_CMODE_SPEECH_EFR;
+		mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, data);
+		break;
+	case MNCC_ALERT_IND:
+		rc = mncc_alert_ind(call, msg_type, arg);
+		break;
+	case MNCC_NOTIFY_IND:
+		rc = mncc_notify_ind(call, msg_type, arg);
+		break;
+	case MNCC_DISC_IND:
+		rc = mncc_disc_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_IND:
+	case MNCC_REJ_IND:
+		rc = mncc_rel_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_CNF:
+		rc = mncc_rel_cnf(call, msg_type, arg);
+		break;
+	case MNCC_FACILITY_IND:
+		break;
+	case MNCC_START_DTMF_IND:
+		break;
+	case MNCC_STOP_DTMF_IND:
+		break;
+	case MNCC_MODIFY_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_MODIFY_REJ, data);
+		break;
+	case MNCC_MODIFY_CNF:
+		break;
+	case MNCC_HOLD_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_HOLD_REJ, data);
+		break;
+	case MNCC_RETRIEVE_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_RETRIEVE_REJ, data);
+		break;
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+		rc = mncc_rcv_tchf(call, msg_type, arg);
+		break;
+	default:
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref);
+		break;
+	}
+
+out_free:
+	talloc_free(msg);
+
+	return rc;
+}
diff --git a/src/libmsc/mncc_sock.c b/src/libmsc/mncc_sock.c
new file mode 100644
index 0000000..2eef7c8
--- /dev/null
+++ b/src/libmsc/mncc_sock.c
@@ -0,0 +1,337 @@
+/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/protocol/gsm_04_08.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+
+struct mncc_sock_state {
+	struct gsm_network *net;
+	struct bsc_fd listen_bfd;	/* fd for listen socket */
+	struct bsc_fd conn_bfd;		/* fd for connection to lcr */
+};
+
+/* FIXME: avoid this */
+static struct mncc_sock_state *g_state;
+
+/* input from CC code into mncc_sock */
+int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg)
+{
+	struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg);
+	int msg_type = mncc_in->msg_type;
+
+	/* Check if we currently have a MNCC handler connected */
+	if (g_state->conn_bfd.fd < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app "
+			"but socket is gone\n", get_mncc_name(msg_type));
+		if (msg_type != GSM_TCHF_FRAME &&
+		    msg_type != GSM_TCHF_FRAME_EFR) {
+			/* release the request */
+			struct gsm_mncc mncc_out;
+			memset(&mncc_out, 0, sizeof(mncc_out));
+			mncc_out.callref = mncc_in->callref;
+			mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_TEMP_FAILURE);
+			mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out);
+		}
+		/* free the original message */
+		msgb_free(msg);
+		return -1;
+	}
+
+	/* FIXME: check for some maximum queue depth? */
+
+	/* Actually enqueue the message and mark socket write need */
+	msgb_enqueue(&net->upqueue, msg);
+	g_state->conn_bfd.when |= BSC_FD_WRITE;
+	return 0;
+}
+
+void mncc_sock_write_pending(void)
+{
+	g_state->conn_bfd.when |= BSC_FD_WRITE;
+}
+
+/* FIXME: move this to libosmocore */
+int osmo_unixsock_listen(struct bsc_fd *bfd, int type, const char *path);
+
+static void mncc_sock_close(struct mncc_sock_state *state)
+{
+	struct bsc_fd *bfd = &state->conn_bfd;
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n");
+
+	close(bfd->fd);
+	bfd->fd = -1;
+	bsc_unregister_fd(bfd);
+
+	/* re-enable the generation of ACCEPT for new connections */
+	state->listen_bfd.when |= BSC_FD_READ;
+
+	/* FIXME: make sure we don't enqueue anymore */
+
+	/* release all exisitng calls */
+	gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC);
+
+	/* flush the queue */
+	while (!llist_empty(&state->net->upqueue)) {
+		struct msgb *msg = msgb_dequeue(&state->net->upqueue);
+		msgb_free(msg);
+	}
+}
+
+static int mncc_sock_read(struct bsc_fd *bfd)
+{
+	struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+	struct gsm_mncc *mncc_prim;
+	struct msgb *msg;
+	int rc;
+
+	msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx");
+	if (!msg)
+		return -ENOMEM;
+
+	mncc_prim = (struct gsm_mncc *) msg->tail;
+
+	rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+	if (rc == 0)
+		goto close;
+
+	if (rc < 0) {
+		if (errno == EAGAIN)
+			return 0;
+		goto close;
+	}
+
+	rc = mncc_tx_to_cc(state->net, mncc_prim->msg_type, mncc_prim);
+
+	/* as we always synchronously process the message in mncc_send() and
+	 * its callbacks, we can free the message here. */
+	msgb_free(msg);
+
+	return rc;
+
+close:
+	msgb_free(msg);
+	mncc_sock_close(state);
+	return -1;
+}
+
+static int mncc_sock_write(struct bsc_fd *bfd)
+{
+	struct mncc_sock_state *state = bfd->data;
+	struct gsm_network *net = state->net;
+	int rc;
+
+	while (!llist_empty(&net->upqueue)) {
+		struct msgb *msg, *msg2;
+		struct gsm_mncc *mncc_prim;
+
+		/* peek at the beginning of the queue */
+		msg = llist_entry(net->upqueue.next, struct msgb, list);
+		mncc_prim = (struct gsm_mncc *)msg->data;
+
+		bfd->when &= ~BSC_FD_WRITE;
+
+		/* try to send it over the socket */
+		rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+		if (rc == 0)
+			goto close;
+		if (rc < 0) {
+			if (errno == EAGAIN) {
+				bfd->when |= BSC_FD_WRITE;
+				break;
+			}
+			goto close;
+		}
+		/* _after_ we send it, we can deueue */
+		msg2 = msgb_dequeue(&net->upqueue);
+		assert(msg == msg2);
+		msgb_free(msg);
+	}
+	return 0;
+
+close:
+	mncc_sock_close(state);
+
+	return -1;
+}
+
+static int mncc_sock_cb(struct bsc_fd *bfd, unsigned int flags)
+{
+	int rc = 0;
+
+	if (flags & BSC_FD_READ)
+		rc = mncc_sock_read(bfd);
+	if (rc < 0)
+		return rc;
+
+	if (flags & BSC_FD_WRITE)
+		rc = mncc_sock_write(bfd);
+
+	return rc;
+}
+
+/* accept a new connection */
+static int mncc_sock_accept(struct bsc_fd *bfd, unsigned int flags)
+{
+	struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+	struct bsc_fd *conn_bfd = &state->conn_bfd;
+	struct sockaddr_un un_addr;
+	socklen_t len;
+	int rc;
+
+	len = sizeof(un_addr);
+	rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n");
+		return -1;
+	}
+
+	if (conn_bfd->fd >= 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have "
+			"another active connection ?!?\n");
+		/* We already have one MNCC app connected, this is all we support */
+		state->listen_bfd.when &= ~BSC_FD_READ;
+		close(rc);
+		return 0;
+	}
+
+	conn_bfd->fd = rc;
+	conn_bfd->when = BSC_FD_READ;
+	conn_bfd->cb = mncc_sock_cb;
+	conn_bfd->data = state;
+
+	if (bsc_register_fd(conn_bfd) != 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n");
+		close(conn_bfd->fd);
+		conn_bfd->fd = -1;
+		state->listen_bfd.when |= ~BSC_FD_READ;
+		return -1;
+	}
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external "
+		"call control application\n");
+
+	return 0;
+}
+
+
+int mncc_sock_init(struct gsm_network *net)
+{
+	struct mncc_sock_state *state;
+	struct bsc_fd *bfd;
+	int rc;
+
+	state = talloc_zero(tall_bsc_ctx, struct mncc_sock_state);
+	if (!state)
+		return -ENOMEM;
+
+	state->net = net;
+	state->conn_bfd.fd = -1;
+
+	bfd = &state->listen_bfd;
+
+	rc = osmo_unixsock_listen(bfd, SOCK_SEQPACKET, "/tmp/bsc_mncc");
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s\n",
+			strerror(errno));
+		talloc_free(state);
+		return rc;
+	}
+
+	bfd->when = BSC_FD_READ;
+	bfd->cb = mncc_sock_accept;
+	bfd->data = state;
+
+	rc = bsc_register_fd(bfd);
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
+		close(bfd->fd);
+		talloc_free(state);
+		return rc;
+	}
+
+	g_state = state;
+
+	return 0;
+}
+
+/* FIXME: move this to libosmocore */
+int osmo_unixsock_listen(struct bsc_fd *bfd, int type, const char *path)
+{
+	struct sockaddr_un local;
+	unsigned int namelen;
+	int rc;
+
+	bfd->fd = socket(AF_UNIX, type, 0);
+
+	if (bfd->fd < 0) {
+		fprintf(stderr, "Failed to create Unix Domain Socket.\n");
+		return -1;
+	}
+
+	local.sun_family = AF_UNIX;
+	strncpy(local.sun_path, path, sizeof(local.sun_path));
+	local.sun_path[sizeof(local.sun_path) - 1] = '\0';
+	unlink(local.sun_path);
+
+	/* we use the same magic that X11 uses in Xtranssock.c for
+	 * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+	local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+	namelen = SUN_LEN(&local);
+#else
+	namelen = strlen(local.sun_path) +
+		  offsetof(struct sockaddr_un, sun_path);
+#endif
+
+	rc = bind(bfd->fd, (struct sockaddr *) &local, namelen);
+	if (rc != 0) {
+		fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n",
+			local.sun_path);
+		return -1;
+	}
+
+	if (listen(bfd->fd, 0) != 0) {
+		fprintf(stderr, "Failed to listen.\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
new file mode 100644
index 0000000..8c86dcc
--- /dev/null
+++ b/src/libmsc/osmo_msc.c
@@ -0,0 +1,104 @@
+/* main MSC management code... */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/debug.h>
+#include <openbsc/transaction.h>
+
+#include <openbsc/gsm_04_11.h>
+
+static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+	int sapi = dlci & 0x7;
+
+	if (sapi == UM_SAPI_SMS)
+		gsm411_sapi_n_reject(conn);
+}
+
+static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	gsm0408_clear_request(conn, cause);
+	if (conn->put_channel) {
+		conn->put_channel = 0;
+		subscr_put_channel(conn->subscr);
+	}
+	return 1;
+}
+
+static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+			uint16_t chosen_channel)
+{
+	gsm0408_new_conn(conn);
+	gsm0408_dispatch(conn, msg);
+
+	/* TODO: do better */
+	return BSC_API_CONN_POL_ACCEPT;
+}
+
+static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+	gsm0408_dispatch(conn, msg);
+}
+
+static struct bsc_api msc_handler = {
+	.sapi_n_reject = msc_sapi_n_reject,
+	.clear_request = msc_clear_request,
+	.compl_l3 = msc_compl_l3,
+	.dtap  = msc_dtap,
+};
+
+struct bsc_api *msc_bsc_api() {
+	return &msc_handler;
+}
+
+/* lchan release handling */
+void msc_release_connection(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_trans *trans;
+
+	/* skip when we are in release, e.g. due an error */
+	if (conn->in_release)
+		return;
+
+	/* skip releasing of silent calls as they have no transaction */
+	if (conn->silent_call)
+		return;
+
+	/* check if there is a pending operation */
+	if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+		return;
+
+	llist_for_each_entry(trans, &conn->bts->network->trans_list, entry) {
+		if (trans->conn == conn)
+			return;
+	}
+
+	/* no more connections, asking to release the channel */
+	conn->in_release = 1;
+	gsm0808_clear(conn);
+	if (conn->put_channel) {
+		conn->put_channel = 0;
+		subscr_put_channel(conn->subscr);
+	}
+	subscr_con_free(conn);
+}
diff --git a/src/libmsc/rrlp.c b/src/libmsc/rrlp.c
new file mode 100644
index 0000000..ae5ca47
--- /dev/null
+++ b/src/libmsc/rrlp.c
@@ -0,0 +1,105 @@
+/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+/* RRLP msPositionReq, nsBased,
+ *	Accuracy=60, Method=gps, ResponseTime=2, oneSet */
+static const u_int8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 };
+
+/* RRLP msPositionReq, msBasedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const u_int8_t ms_pref_pos_req[]  = { 0x40, 0x02, 0x79, 0x50 };
+
+/* RRLP msPositionReq, msAssistedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const u_int8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 };
+
+static int send_rrlp_req(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_network *net = conn->bts->network;
+	const u_int8_t *req;
+
+	switch (net->rrlp.mode) {
+	case RRLP_MODE_MS_BASED:
+		req = ms_based_pos_req;
+		break;
+	case RRLP_MODE_MS_PREF:
+		req = ms_pref_pos_req;
+		break;
+	case RRLP_MODE_ASS_PREF:
+		req = ass_pref_pos_req;
+		break;
+	case RRLP_MODE_NONE:
+	default:
+		return 0;
+	}
+
+	return gsm48_send_rr_app_info(conn, 0x00,
+				      sizeof(ms_based_pos_req), req);
+}
+
+static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_subscriber_connection *conn;
+
+	switch (signal) {
+	case S_SUBSCR_ATTACHED:
+		/* A subscriber has attached. */
+		subscr = signal_data;
+		conn = connection_for_subscr(subscr);
+		if (!conn)
+			break;
+		send_rrlp_req(conn);
+		break;
+	}
+	return 0;
+}
+
+static int paging_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct paging_signal_data *psig_data = signal_data;
+
+	switch (signal) {
+	case S_PAGING_SUCCEEDED:
+		/* A subscriber has attached. */
+		send_rrlp_req(psig_data->conn);
+		break;
+	case S_PAGING_EXPIRED:
+		break;
+	}
+	return 0;
+}
+
+void on_dso_load_rrlp(void)
+{
+	register_signal_handler(SS_SUBSCR, subscr_sig_cb, NULL);
+	register_signal_handler(SS_PAGING, paging_sig_cb, NULL);
+}
diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c
new file mode 100644
index 0000000..64ebdfd
--- /dev/null
+++ b/src/libmsc/silent_call.c
@@ -0,0 +1,143 @@
+/* GSM silent call feature */
+
+/*
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/osmo_msc.h>
+
+/* paging of the requested subscriber has completed */
+static int paging_cb_silent(unsigned int hooknum, unsigned int event,
+			    struct msgb *msg, void *_conn, void *_data)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct scall_signal_data sigdata;
+	int rc = 0;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	DEBUGP(DSMS, "paging_cb_silent: ");
+
+	sigdata.conn = conn;
+	sigdata.data = _data;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		DEBUGPC(DSMS, "success, using Timeslot %u on ARFCN %u\n",
+			conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
+		conn->silent_call = 1;
+		/* increment lchan reference count */
+		dispatch_signal(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_BUSY:
+	case GSM_PAGING_OOM:
+		DEBUGP(DSMS, "expired\n");
+		dispatch_signal(SS_SCALL, S_SCALL_EXPIRED, &sigdata);
+		break;
+	default:
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/* receive a layer 3 message from a silent call */
+int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	/* FIXME: do something like sending it through a UDP port */
+	return 0;
+}
+
+struct msg_match {
+	u_int8_t pdisc;
+	u_int8_t msg_type;
+};
+
+/* list of messages that are handled inside OpenBSC, even in a silent call */
+static const struct msg_match silent_call_accept[] = {
+	{ GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST },
+	{ GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ },
+};
+
+/* decide if we need to reroute a message as part of a silent call */
+int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t pdisc = gh->proto_discr & 0x0f;
+	int i;
+
+	/* if we're not part of a silent call, never reroute */
+	if (!conn->silent_call)
+		return 0;
+
+	/* check if we are a special message that is handled in openbsc */
+	for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) {
+		if (silent_call_accept[i].pdisc == pdisc &&
+		    silent_call_accept[i].msg_type == gh->msg_type)
+			return 0;
+	}
+
+	/* otherwise, reroute */
+	return 1;
+}
+
+
+/* initiate a silent call with a given subscriber */
+int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type)
+{
+	int rc;
+
+	rc = paging_request(subscr->net, subscr, type,
+			    paging_cb_silent, data);
+	return rc;
+}
+
+/* end a silent call with a given subscriber */
+int gsm_silent_call_stop(struct gsm_subscriber *subscr)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = connection_for_subscr(subscr);
+	if (!conn)
+		return -EINVAL;
+
+	/* did we actually establish a silent call for this guy? */
+	if (!conn->silent_call)
+		return -EINVAL;
+
+	conn->silent_call = 0;
+	msc_release_connection(conn);
+
+	return 0;
+}
diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c
new file mode 100644
index 0000000..079755d
--- /dev/null
+++ b/src/libmsc/sms_queue.c
@@ -0,0 +1,479 @@
+/* SMS queue to continously attempt to deliver SMS */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * The difficulty of such a queue is to send a lot of SMS without
+ * overloading the paging subsystem and the database and other users
+ * of the MSC. To make the best use we would need to know the number
+ * of pending paging requests, then throttle the number of SMS we
+ * want to send and such.
+ * We will start with a very simple SMS Queue and then try to speed
+ * things up by collecting data from other parts of the system.
+ */
+
+#include <openbsc/sms_queue.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+
+#include <osmocore/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+/*
+ * One pending SMS that we wait for.
+ */
+struct gsm_sms_pending {
+	struct llist_head entry;
+
+	struct gsm_subscriber *subscr;
+	unsigned long long sms_id;
+	int failed_attempts;
+	int resend;
+};
+
+struct gsm_sms_queue {
+	struct timer_list resend_pending;
+	struct timer_list push_queue;
+	struct gsm_network *network;
+	int max_fail;
+	int max_pending;
+	int pending;
+
+	struct llist_head pending_sms;
+	unsigned long long last_subscr_id;
+};
+
+static int sms_subscr_cb(unsigned int, unsigned int, void *, void *);
+static int sms_sms_cb(unsigned int, unsigned int, void *, void *);
+
+static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq,
+						struct gsm_sms *sms)
+{
+	struct gsm_sms_pending *pending;
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+		if (pending->sms_id == sms->id)
+			return pending;
+	}
+
+	return NULL;
+}
+
+static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms)
+{
+	return sms_find_pending(smsq, sms) != NULL;
+}
+
+static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq,
+				     struct gsm_subscriber *subscr)
+{
+	struct gsm_sms_pending *pending;
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+		if (pending->subscr == subscr)
+			return 1;
+	}
+
+	return 0;
+}
+
+static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq,
+						struct gsm_sms *sms)
+{
+	struct gsm_sms_pending *pending;
+
+	pending = talloc_zero(smsq, struct gsm_sms_pending);
+	if (!pending)
+		return NULL;
+
+	pending->subscr = subscr_get(sms->receiver);
+	pending->sms_id = sms->id;
+	return pending;
+}
+
+static void sms_pending_free(struct gsm_sms_pending *pending)
+{
+	subscr_put(pending->subscr);
+	llist_del(&pending->entry);
+	talloc_free(pending);
+}
+
+static void sms_pending_resend(struct gsm_sms_pending *pending)
+{
+	struct gsm_sms_queue *smsq;
+	LOGP(DSMS, LOGL_DEBUG,
+	     "Scheduling resend of SMS %llu.\n", pending->sms_id);
+
+	pending->resend = 1;
+
+	smsq = pending->subscr->net->sms_queue;
+	if (bsc_timer_pending(&smsq->resend_pending))
+		return;
+
+	bsc_schedule_timer(&smsq->resend_pending, 1, 0);
+}
+
+static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error)
+{
+	struct gsm_sms_queue *smsq;
+
+	LOGP(DSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n",
+	     pending->sms_id, pending->failed_attempts);
+
+	smsq = pending->subscr->net->sms_queue;
+	if (++pending->failed_attempts < smsq->max_fail)
+		return sms_pending_resend(pending);
+
+	if (paging_error) {
+		LOGP(DSMS, LOGL_NOTICE,
+		     "Subscriber %llu is not reachable. Setting LAC=0.\n", pending->subscr->id);
+		pending->subscr->lac = GSM_LAC_RESERVED_DETACHED;
+		db_sync_subscriber(pending->subscr);
+
+		/* Workaround a failing sync */
+		db_subscriber_update(pending->subscr);
+	}
+
+	sms_pending_free(pending);
+	smsq->pending -= 1;
+	sms_queue_trigger(smsq);
+}
+
+/*
+ * Resend all SMS that are scheduled for a resend. This is done to
+ * avoid an immediate failure.
+ */
+static void sms_resend_pending(void *_data)
+{
+	struct gsm_sms_pending *pending, *tmp;
+	struct gsm_sms_queue *smsq = _data;
+
+	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+		struct gsm_sms *sms;
+		if (!pending->resend)
+			continue;
+
+		sms = db_sms_get(smsq->network, pending->sms_id);
+
+		/* the sms is gone? Move to the next */
+		if (!sms) {
+			sms_pending_free(pending);
+			smsq->pending -= 1;
+			sms_queue_trigger(smsq);
+		} else {
+			pending->resend = 0;
+			gsm411_send_sms_subscr(sms->receiver, sms);
+		}
+	}
+}
+
+static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq)
+{
+	struct gsm_sms *sms;
+
+	sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10);
+	if (sms) {
+		smsq->last_subscr_id = sms->receiver->id + 1;
+		return sms;
+	}
+
+	/* need to wrap around */
+	smsq->last_subscr_id = 0;
+	sms = db_sms_get_unsent_by_subscr(smsq->network,
+					  smsq->last_subscr_id, 10);
+	if (sms)
+		smsq->last_subscr_id = sms->receiver->id + 1;
+	return sms;
+}
+
+/**
+ * I will submit up to max_pending - pending SMS to the
+ * subsystem.
+ */
+static void sms_submit_pending(void *_data)
+{
+	struct gsm_sms_queue *smsq = _data;
+	int attempts = smsq->max_pending - smsq->pending;
+	int initialized = 0;
+	unsigned long long first_sub = 0;
+	int attempted = 0, rounds = 0;
+
+	LOGP(DSMS, LOGL_NOTICE, "Attempting to send %d SMS\n", attempts);
+
+	do {
+		struct gsm_sms_pending *pending;
+		struct gsm_sms *sms;
+
+
+		sms = take_next_sms(smsq);
+		if (!sms)
+			break;
+
+		rounds += 1;
+
+		/*
+		 * This code needs to detect a loop. It assumes that no SMS
+		 * will vanish during the time this is executed. We will remember
+		 * the id of the first GSM subscriber we see and then will
+		 * compare this. The Database code should make sure that we will
+		 * see all other subscribers first before seeing this one again.
+		 *
+		 * It is always scary to have an infinite loop like this.
+		 */
+		if (!initialized) {
+			first_sub = sms->receiver->id;
+			initialized = 1;
+		} else if (first_sub == sms->receiver->id) {
+			sms_free(sms);
+			break;
+		}
+
+		/* no need to send a pending sms */
+		if (sms_is_in_pending(smsq, sms)) {
+			LOGP(DSMS, LOGL_DEBUG,
+			     "SMSqueue with pending sms: %llu. Skipping\n", sms->id);
+			sms_free(sms);
+			continue;
+		}
+
+		/* no need to send a SMS with the same receiver */
+		if (sms_subscriber_is_pending(smsq, sms->receiver)) {
+			LOGP(DSMS, LOGL_DEBUG,
+			     "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id);
+			sms_free(sms);
+			continue;
+		}
+
+		pending = sms_pending_from(smsq, sms);
+		if (!pending) {
+			LOGP(DSMS, LOGL_ERROR,
+			     "Failed to create pending SMS entry.\n");
+			sms_free(sms);
+			continue;
+		}
+
+		attempted += 1;
+		smsq->pending += 1;
+		llist_add_tail(&pending->entry, &smsq->pending_sms);
+		gsm411_send_sms_subscr(sms->receiver, sms);
+	} while (attempted < attempts && rounds < 1000);
+
+	LOGP(DSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds);
+}
+
+/*
+ * Kick off the queue again.
+ */
+int sms_queue_trigger(struct gsm_sms_queue *smsq)
+{
+	if (bsc_timer_pending(&smsq->push_queue))
+		return 0;
+
+	bsc_schedule_timer(&smsq->push_queue, 1, 0);
+	return 0;
+}
+
+int sms_queue_start(struct gsm_network *network, int max_pending)
+{
+	struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue);
+	if (!sms) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n");
+		return -1;
+	}
+
+	register_signal_handler(SS_SUBSCR, sms_subscr_cb, network);
+	register_signal_handler(SS_SMS, sms_sms_cb, network);
+
+	network->sms_queue = sms;
+	INIT_LLIST_HEAD(&sms->pending_sms);
+	sms->max_fail = 1;
+	sms->network = network;
+	sms->max_pending = max_pending;
+	sms->push_queue.data = sms;
+	sms->push_queue.cb = sms_submit_pending;
+	sms->resend_pending.data = sms;
+	sms->resend_pending.cb = sms_resend_pending;
+
+	sms_submit_pending(sms);
+
+	return 0;
+}
+
+static int sub_ready_for_sm(struct gsm_subscriber *subscr)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_sms *sms;
+
+	/* A subscriber has attached. Check if there are
+	 * any pending SMS for him to be delivered */
+	conn = connection_for_subscr(subscr);
+	if (!conn)
+		return -1;
+	sms = db_sms_get_unsent_for_subscr(subscr);
+	if (!sms)
+		return -1;
+	gsm411_send_sms(conn, sms);
+	return 0;
+}
+
+static int sms_subscr_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	/* this is readyForSM */
+	return sub_ready_for_sm(subscr);
+}
+
+static int sms_sms_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_network *network = handler_data;
+	struct sms_signal_data *sig_sms = signal_data;
+	struct gsm_sms_pending *pending;
+
+	/* We got a new SMS and maybe should launch the queue again. */
+	if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) {
+		sms_queue_trigger(network->sms_queue);
+		return 0;
+	}
+
+	if (!sig_sms->sms)
+		return -1;
+
+
+	/*
+	 * Find the entry of our queue. The SMS subsystem will submit
+	 * sms that are not in our control as we just have a channel
+	 * open anyway.
+	 */
+	pending = sms_find_pending(network->sms_queue, sig_sms->sms);
+	if (!pending)
+		return 0;
+
+	switch (signal) {
+	case S_SMS_DELIVERED:
+		/*
+		 * Create place for a new SMS but keep the pending data
+		 * so we will not attempt to send the SMS for this subscriber
+		 * as we still have an open channel and will attempt to submit
+		 * SMS to it anyway.
+		 */
+		network->sms_queue->pending -= 1;
+		sms_submit_pending(network->sms_queue);
+		sms_pending_free(pending);
+		break;
+	case S_SMS_MEM_EXCEEDED:
+		network->sms_queue->pending -= 1;
+		sms_pending_free(pending);
+		sms_queue_trigger(network->sms_queue);
+		break;
+	case S_SMS_UNKNOWN_ERROR:
+		/*
+		 * There can be many reasons for this failure. E.g. the paging
+		 * timed out, the subscriber was not paged at all, or there was
+		 * a protocol error. The current strategy is to try sending the
+		 * next SMS for busy/oom and to retransmit when we have paged.
+		 *
+		 * When the paging expires three times we will disable the
+		 * subscriber. If we have some kind of other transmit error we
+		 * should flag the SMS as bad.
+		 */
+		switch (sig_sms->paging_result) {
+		case 0:
+			/* BAD SMS? */
+			db_sms_inc_deliver_attempts(sig_sms->sms);
+			sms_pending_failed(pending, 0);
+			break;
+		case GSM_PAGING_EXPIRED:
+			sms_pending_failed(pending, 1);
+			break;
+
+		case GSM_PAGING_OOM:
+		case GSM_PAGING_BUSY:
+			network->sms_queue->pending -= 1;
+			sms_pending_free(pending);
+			sms_queue_trigger(network->sms_queue);
+			break;
+		default:
+			LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n",
+			     sig_sms->paging_result);
+		}
+		break;
+	default:
+		LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n",
+		     sig_sms->paging_result);
+	}
+
+	return 0;
+}
+
+/* VTY helper functions */
+int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty)
+{
+	struct gsm_sms_pending *pending;
+
+	vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s",
+		smsq->max_pending, smsq->pending, VTY_NEWLINE);
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry)
+		vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s",
+			pending->subscr->id, pending->sms_id,
+			pending->failed_attempts, VTY_NEWLINE);
+	return 0;
+}
+
+int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending)
+{
+	LOGP(DSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n",
+	     smsq->max_pending, max_pending);
+	smsq->max_pending = max_pending;
+	return 0;
+}
+
+int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail)
+{
+	LOGP(DSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n",
+	     smsq->max_fail, max_fail);
+	smsq->max_fail = max_fail;
+	return 0;
+}
+
+int sms_queue_clear(struct gsm_sms_queue *smsq)
+{
+	struct gsm_sms_pending *pending, *tmp;
+
+	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+		LOGP(DSMS, LOGL_NOTICE,
+		     "SMSqueue clearing for sub %llu\n", pending->subscr->id);
+		sms_pending_free(pending);
+	}
+
+	smsq->pending = 0;
+	return 0;
+}
diff --git a/src/libmsc/token_auth.c b/src/libmsc/token_auth.c
new file mode 100644
index 0000000..3404dd4
--- /dev/null
+++ b/src/libmsc/token_auth.c
@@ -0,0 +1,153 @@
+/* SMS based token authentication for ad-hoc GSM networks */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+
+#define TOKEN_SMS_TEXT "HAR 2009 GSM.  Register at http://har2009.gnumonks.org/ " \
+			"Your IMSI is %s, auth token is %08X, phone no is %s."
+
+static char *build_sms_string(struct gsm_subscriber *subscr, u_int32_t token)
+{
+	char *sms_str;
+	unsigned int len;
+
+	len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT);
+	sms_str = talloc_size(tall_bsc_ctx, len);
+	if (!sms_str)
+		return NULL;
+
+	snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token,
+		 subscr->extension);
+	sms_str[len-1] = '\0';
+
+	return sms_str;
+}
+
+static int token_subscr_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+	struct gsm_sms *sms;
+	int rc = 0;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	if (subscr->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+	if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) {
+		u_int32_t token;
+		char *sms_str;
+
+		/* we've seen this subscriber for the first time. */
+		rc = db_subscriber_alloc_token(subscr, &token);
+		if (rc != 0) {
+			rc = -EIO;
+			goto unauth;
+		}
+
+		sms_str = build_sms_string(subscr, token);
+		if (!sms_str) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		sms = sms_from_text(subscr, 0, sms_str);
+		talloc_free(sms_str);
+		if (!sms) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		rc = gsm411_send_sms_subscr(subscr, sms);
+
+		/* FIXME: else, delete the subscirber from database */
+unauth:
+
+		/* make sure we don't allow him in again unless he clicks the web UI */
+		subscr->authorized = 0;
+		db_sync_subscriber(subscr);
+		if (rc) {
+			struct gsm_subscriber_connection *conn = connection_for_subscr(subscr);
+			if (conn) {
+				u_int8_t auth_rand[16];
+				/* kick the subscriber off the network */
+				gsm48_tx_mm_auth_req(conn, auth_rand, 0);
+				gsm48_tx_mm_auth_rej(conn);
+				/* FIXME: close the channel early ?*/
+				//gsm48_send_rr_Release(lchan);
+			}
+		}
+	}
+
+	return rc;
+}
+
+static int token_sms_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct sms_signal_data *sig = signal_data;
+	struct gsm_sms *sms = sig->sms;;
+	struct gsm_subscriber_connection *conn;
+	u_int8_t auth_rand[16];
+
+
+	if (signal != S_SMS_DELIVERED)
+		return 0;
+
+
+	/* these are not the droids we've been looking for */
+	if (!sms->receiver ||
+	    !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT))
+		return 0;
+
+
+	if (sms->receiver->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+
+	conn = connection_for_subscr(sms->receiver);
+	if (conn) {
+		/* kick the subscriber off the network */
+		gsm48_tx_mm_auth_req(conn, auth_rand, 0);
+		gsm48_tx_mm_auth_rej(conn);
+		/* FIXME: close the channel early ?*/
+		//gsm48_send_rr_Release(lchan);
+	}
+
+	return 0;
+}
+
+//static __attribute__((constructor)) void on_dso_load_token(void)
+void on_dso_load_token(void)
+{
+	register_signal_handler(SS_SUBSCR, token_subscr_cb, NULL);
+	register_signal_handler(SS_SMS, token_sms_cb, NULL);
+}
diff --git a/src/libmsc/ussd.c b/src/libmsc/ussd.c
new file mode 100644
index 0000000..72f26bd
--- /dev/null
+++ b/src/libmsc/ussd.c
@@ -0,0 +1,79 @@
+/* Network-specific handling of mobile-originated USSDs. */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* This module defines the network-specific handling of mobile-originated
+   USSD messages. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/osmo_msc.h>
+
+/* Declarations of USSD strings to be recognised */
+const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
+
+/* Forward declarations of network-specific handler functions */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req);
+
+
+/* Entrypoint - handler function common to all mobile-originated USSDs */
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	int rc;
+	struct ussd_request req;
+	struct gsm48_hdr *gh;
+
+	memset(&req, 0, sizeof(req));
+	gh = msgb_l3(msg);
+	rc = gsm0480_decode_ussd_request(gh, msgb_l3len(msg), &req);
+	if (req.text[0] == 0xFF)  /* Release-Complete */
+		return 0;
+
+	if (strstr(USSD_TEXT_OWN_NUMBER, req.text) != NULL) {
+		DEBUGP(DMM, "USSD: Own number requested\n");
+		rc = send_own_number(conn, msg, &req);
+	} else {
+		DEBUGP(DMM, "Unhandled USSD %s\n", req.text);
+		rc = gsm0480_send_ussd_reject(conn, msg, &req);
+	}
+
+	/* check if we can release it */
+	msc_release_connection(conn);
+	return rc;
+}
+
+/* A network-specific handler function */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req)
+{
+	char *own_number = conn->subscr->extension;
+	char response_string[GSM_EXTENSION_LENGTH + 20];
+
+	/* Need trailing CR as EOT character */
+	snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number);
+	return gsm0480_send_ussd_response(conn, msg, response_string, req);
+}
diff --git a/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c
new file mode 100644
index 0000000..a38d15b
--- /dev/null
+++ b/src/libmsc/vty_interface_layer3.c
@@ -0,0 +1,790 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/utils.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/sms_queue.h>
+
+extern struct gsm_network *gsmnet_from_vty(struct vty *v);
+
+static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr, int pending)
+{
+	int rc;
+	struct gsm_auth_info ainfo;
+	struct gsm_auth_tuple atuple;
+
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (subscr->name)
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (subscr->extension)
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	vty_out(vty, "    LAC: %d/0x%x%s",
+		subscr->lac, subscr->lac, VTY_NEWLINE);
+	if (subscr->imsi)
+		vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 algorithm id: %d%s",
+			ainfo.auth_algo, VTY_NEWLINE);
+		vty_out(vty, "    A3A8 Ki: %s%s",
+			hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len),
+			VTY_NEWLINE);
+	}
+
+	rc = db_get_lastauthtuple_for_subscr(&atuple, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 last tuple (used %d times):%s",
+			atuple.use_count, VTY_NEWLINE);
+		vty_out(vty, "     seq # : %d%s",
+			atuple.key_seq, VTY_NEWLINE);
+		vty_out(vty, "     RAND  : %s%s",
+			hexdump(atuple.rand, sizeof(atuple.rand)),
+			VTY_NEWLINE);
+		vty_out(vty, "     SRES  : %s%s",
+			hexdump(atuple.sres, sizeof(atuple.sres)),
+			VTY_NEWLINE);
+		vty_out(vty, "     Kc    : %s%s",
+			hexdump(atuple.kc, sizeof(atuple.kc)),
+			VTY_NEWLINE);
+	}
+	if (pending)
+		vty_out(vty, "    Pending: %d%s",
+			subscr_pending_requests(subscr), VTY_NEWLINE);
+
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+
+/* Subscriber */
+DEFUN(show_subscr_cache,
+      show_subscr_cache_cmd,
+      "show subscriber cache",
+	SHOW_STR "Display contents of subscriber cache\n")
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, &active_subscribers, entry) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_full_vty(vty, subscr, 0);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(sms_send_pend,
+      sms_send_pend_cmd,
+      "sms send pending",
+      "Send all pending SMS")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_sms *sms;
+	int id = 0;
+
+	while (1) {
+		sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX);
+		if (!sms)
+			break;
+
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+		id = sms->receiver->id + 1;
+	}
+
+	return CMD_SUCCESS;
+}
+
+static int _send_sms_str(struct gsm_subscriber *receiver, char *str,
+			 u_int8_t tp_pid)
+{
+	struct gsm_sms *sms;
+
+	sms = sms_from_text(receiver, 0, str);
+	sms->protocol_id = tp_pid;
+
+	/* store in database for the queue */
+	if (db_sms_store(sms) != 0) {
+		LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		sms_free(sms);
+		return CMD_WARNING;
+	}
+
+	sms_free(sms);
+	sms_queue_trigger(receiver->net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet,
+						 const char *type,
+						 const char *id)
+{
+	if (!strcmp(type, "extension"))
+		return subscr_get_by_extension(gsmnet, id);
+	else if (!strcmp(type, "imsi"))
+		return subscr_get_by_imsi(gsmnet, id);
+	else if (!strcmp(type, "tmsi"))
+		return subscr_get_by_tmsi(gsmnet, atoi(id));
+	else if (!strcmp(type, "id"))
+		return subscr_get_by_id(gsmnet, atoi(id));
+
+	return NULL;
+}
+#define SUBSCR_TYPES "(extension|imsi|tmsi|id)"
+#define SUBSCR_HELP "Operations on a Subscriber\n"			\
+	"Identify subscriber by his extension (phone number)\n"		\
+	"Identify subscriber by his IMSI\n"				\
+	"Identify subscriber by his TMSI\n"				\
+	"Identify subscriber by his database ID\n"			\
+	"Identifier for the subscriber\n"
+
+DEFUN(show_subscr,
+      show_subscr_cmd,
+      "show subscriber " SUBSCR_TYPES " ID",
+	SHOW_STR SUBSCR_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+				get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_dump_full_vty(vty, subscr, 1);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_pending_sms,
+      subscriber_send_pending_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID sms pending send",
+	SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct gsm_sms *sms;
+
+	sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX);
+	if (sms)
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_sms,
+      subscriber_send_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID sms send .LINE",
+	SUBSCR_HELP "SMS Operations\n" "Send SMS\n" "Actual SMS Text")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *str;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	str = argv_concat(argv, argc, 2);
+	rc = _send_sms_str(subscr, str, 0);
+	talloc_free(str);
+
+	subscr_put(subscr);
+
+	return rc;
+}
+
+DEFUN(subscriber_silent_sms,
+      subscriber_silent_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-sms send .LINE",
+	SUBSCR_HELP
+	"Silent SMS Operation\n" "Send Silent SMS\n" "Actual SMS text\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *str;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	str = argv_concat(argv, argc, 2);
+	rc = _send_sms_str(subscr, str, 64);
+	talloc_free(str);
+
+	subscr_put(subscr);
+
+	return rc;
+}
+
+#define CHAN_TYPES "(any|tch/f|tch/any|sdcch)"
+#define CHAN_TYPE_HELP 			\
+		"Any channel\n"		\
+		"TCH/F channel\n"	\
+		"Any TCH channel\n"	\
+		"SDCCH channel\n"
+
+DEFUN(subscriber_silent_call_start,
+      subscriber_silent_call_start_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-call start (any|tch/f|tch/any|sdcch)",
+	SUBSCR_HELP "Silent call operation\n" "Start silent call\n"
+	CHAN_TYPE_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int rc, type;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[2], "tch/f"))
+		type = RSL_CHANNEED_TCH_F;
+	else if (!strcmp(argv[2], "tch/any"))
+		type = RSL_CHANNEED_TCH_ForH;
+	else if (!strcmp(argv[2], "sdcch"))
+		type = RSL_CHANNEED_SDCCH;
+	else
+		type = RSL_CHANNEED_ANY;	/* Defaults to ANY */
+
+	rc = gsm_silent_call_start(subscr, vty, type);
+	if (rc <= 0) {
+		vty_out(vty, "%% Subscriber not attached%s",
+			VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_silent_call_stop,
+      subscriber_silent_call_stop_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-call stop",
+	SUBSCR_HELP "Silent call operation\n" "Stop silent call\n"
+	CHAN_TYPE_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = gsm_silent_call_stop(subscr);
+	if (rc < 0) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_ussd_notify,
+      subscriber_ussd_notify_cmd,
+      "subscriber " SUBSCR_TYPES " ID ussd-notify (0|1|2) .TEXT",
+      SUBSCR_HELP "USSD Notify\n"
+      "Subscriber ID\n"
+      "Alerting Level\n"
+      "Text Message to send\n")
+{
+	char *text;
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int level;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	level = atoi(argv[2]);
+	text = argv_concat(argv, argc, 3);
+	if (!text) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	conn = connection_for_subscr(subscr);
+	if (!conn) {
+		vty_out(vty, "%% An active connection is required for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		subscr_put(subscr);
+		talloc_free(text);
+		return CMD_WARNING;
+	}
+
+	gsm0480_send_ussdNotify(conn, level, text);
+	gsm0480_send_releaseComplete(conn);
+
+	subscr_put(subscr);
+	talloc_free(text);
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_authorizde,
+      ena_subscr_authorized_cmd,
+      "subscriber " SUBSCR_TYPES " ID authorized (0|1)",
+	SUBSCR_HELP "(De-)Authorize subscriber in HLR\n"
+	"Subscriber should NOT be authorized\n"
+	"Subscriber should be authorized\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr->authorized = atoi(argv[2]);
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_name,
+      ena_subscr_name_cmd,
+      "subscriber " SUBSCR_TYPES " ID name .NAME",
+	SUBSCR_HELP "Set the name of the subscriber\n"
+	"Name of the Subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *name;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	name = argv_concat(argv, argc, 2);
+	if (!name) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	strncpy(subscr->name, name, sizeof(subscr->name));
+	talloc_free(name);
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_extension,
+      ena_subscr_extension_cmd,
+      "subscriber " SUBSCR_TYPES " ID extension EXTENSION",
+	SUBSCR_HELP "Set the extension (phone number) of the subscriber\n"
+	"Extension (phone number)\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *name = argv[2];
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	strncpy(subscr->extension, name, sizeof(subscr->name));
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_clear,
+      ena_subscr_clear_cmd,
+      "subscriber " SUBSCR_TYPES " ID clear-requests",
+	SUBSCR_HELP "Clear the paging requests for this subscriber\n")
+{
+	int del;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	del = subscr_pending_clear(subscr);
+	vty_out(vty, "Cleared %d pending requests.%s", del, VTY_NEWLINE);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_pend,
+      ena_subscr_pend_cmd,
+      "subscriber " SUBSCR_TYPES " ID show-pending",
+	SUBSCR_HELP "Clear the paging requests for this subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_pending_dump(subscr, vty);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_kick,
+      ena_subscr_kick_cmd,
+      "subscriber " SUBSCR_TYPES " ID kick-pending",
+	SUBSCR_HELP "Clear the paging requests for this subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_pending_kick(subscr);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+#define A3A8_ALG_TYPES "(none|xor|comp128v1)"
+#define A3A8_ALG_HELP 			\
+	"Use No A3A8 algorithm\n"	\
+	"Use XOR algorithm\n"		\
+	"Use COMP128v1 algorithm\n"
+
+DEFUN(ena_subscr_a3a8,
+      ena_subscr_a3a8_cmd,
+      "subscriber " SUBSCR_TYPES " ID a3a8 " A3A8_ALG_TYPES " [KI]",
+      SUBSCR_HELP "Set a3a8 parameters for the subscriber\n"
+      A3A8_ALG_HELP "Encryption Key Ki\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *alg_str = argv[2];
+	const char *ki_str = argc == 4 ? argv[3] : NULL;
+	struct gsm_auth_info ainfo;
+	int rc, minlen, maxlen;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcasecmp(alg_str, "none")) {
+		ainfo.auth_algo = AUTH_ALGO_NONE;
+		minlen = maxlen = 0;
+	} else if (!strcasecmp(alg_str, "xor")) {
+		ainfo.auth_algo = AUTH_ALGO_XOR;
+		minlen = A38_XOR_MIN_KEY_LEN;
+		maxlen = A38_XOR_MAX_KEY_LEN;
+	} else if (!strcasecmp(alg_str, "comp128v1")) {
+		ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+		minlen = maxlen = A38_COMP128_KEY_LEN;
+	} else {
+		/* Unknown method */
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	if (ki_str) {
+		rc = hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+		if ((rc > maxlen) || (rc < minlen)) {
+			subscr_put(subscr);
+			return CMD_WARNING;
+		}
+		ainfo.a3a8_ki_len = rc;
+	} else {
+		ainfo.a3a8_ki_len = 0;
+		if (minlen) {
+			subscr_put(subscr);
+			return CMD_WARNING;
+		}
+	}
+
+	rc = db_sync_authinfo_for_subscr(
+		ainfo.auth_algo == AUTH_ALGO_NONE ? NULL : &ainfo,
+		subscr);
+
+	/* the last tuple probably invalid with the new auth settings */
+	db_sync_lastauthtuple_for_subscr(NULL, subscr);
+	subscr_put(subscr);
+
+	return rc ? CMD_WARNING : CMD_SUCCESS;
+}
+
+DEFUN(subscriber_purge,
+      subscriber_purge_cmd,
+      "subscriber purge-inactive",
+      "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	int purged;
+
+	purged = subscr_purge_inactive(net);
+	vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_update,
+      subscriber_update_cmd,
+      "subscriber " SUBSCR_TYPES " ID update",
+      SUBSCR_HELP "Update the subscriber data from the dabase.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_update_from_db(subscr);
+	subscr_put(subscr);
+	return CMD_SUCCESS;
+}
+
+static int scall_cbfn(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct scall_signal_data *sigdata = signal_data;
+	struct vty *vty = sigdata->data;
+
+	switch (signal) {
+	case S_SCALL_SUCCESS:
+		vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s",
+			sigdata->conn->lchan->ts->trx->arfcn, sigdata->conn->lchan->ts->nr,
+			VTY_NEWLINE);
+		break;
+	case S_SCALL_EXPIRED:
+		vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE);
+		break;
+	}
+	return 0;
+}
+
+DEFUN(show_stats,
+      show_stats_cmd,
+      "show statistics",
+	SHOW_STR "Display network statistics\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	openbsc_vty_print_statistics(vty, net);
+	vty_out(vty, "Channel Requests        : %lu total, %lu no channel%s",
+		counter_get(net->stats.chreq.total),
+		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
+	vty_out(vty, "Location Update         : %lu attach, %lu normal, %lu periodic%s",
+		counter_get(net->stats.loc_upd_type.attach),
+		counter_get(net->stats.loc_upd_type.normal),
+		counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE);
+	vty_out(vty, "IMSI Detach Indications : %lu%s",
+		counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE);
+	vty_out(vty, "Location Update Response: %lu accept, %lu reject%s",
+		counter_get(net->stats.loc_upd_resp.accept),
+		counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE);
+	vty_out(vty, "Handover                : %lu attempted, %lu no_channel, %lu timeout, "
+		"%lu completed, %lu failed%s",
+		counter_get(net->stats.handover.attempted),
+		counter_get(net->stats.handover.no_channel),
+		counter_get(net->stats.handover.timeout),
+		counter_get(net->stats.handover.completed),
+		counter_get(net->stats.handover.failed), VTY_NEWLINE);
+	vty_out(vty, "SMS MO                  : %lu submitted, %lu no receiver%s",
+		counter_get(net->stats.sms.submitted),
+		counter_get(net->stats.sms.no_receiver), VTY_NEWLINE);
+	vty_out(vty, "SMS MT                  : %lu delivered, %lu no memory, %lu other error%s",
+		counter_get(net->stats.sms.delivered),
+		counter_get(net->stats.sms.rp_err_mem),
+		counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE);
+	vty_out(vty, "MO Calls                : %lu setup, %lu connect ack%s",
+		counter_get(net->stats.call.mo_setup),
+		counter_get(net->stats.call.mo_connect_ack), VTY_NEWLINE);
+	vty_out(vty, "MT Calls                : %lu setup, %lu connect%s",
+		counter_get(net->stats.call.mt_setup),
+		counter_get(net->stats.call.mt_connect), VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_smsqueue,
+      show_smsqueue_cmd,
+      "show sms-queue",
+      SHOW_STR "Display SMSqueue statistics\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_stats(net->sms_queue, vty);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_trigger,
+      smsqueue_trigger_cmd,
+      "sms-queue trigger",
+      "SMS Queue\n" "Trigger sending messages\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_trigger(net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_max,
+      smsqueue_max_cmd,
+      "sms-queue max-pending <1-500>",
+      "SMS Queue\n" "SMS to attempt to deliver at the same time\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_set_max_pending(net->sms_queue, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_clear,
+      smsqueue_clear_cmd,
+      "sms-queue clear",
+      "SMS Queue\n" "Clear the queue of pending SMS\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_clear(net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_fail,
+      smsqueue_fail_cmd,
+      "sms-queue max-failure <1-500>",
+      "SMS Queue\n" "Set maximum amount of failures\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_set_max_failure(net->sms_queue, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+	register_signal_handler(SS_SCALL, scall_cbfn, NULL);
+
+	install_element_ve(&show_subscr_cmd);
+	install_element_ve(&show_subscr_cache_cmd);
+
+	install_element_ve(&sms_send_pend_cmd);
+
+	install_element_ve(&subscriber_send_sms_cmd);
+	install_element_ve(&subscriber_silent_sms_cmd);
+	install_element_ve(&subscriber_silent_call_start_cmd);
+	install_element_ve(&subscriber_silent_call_stop_cmd);
+	install_element_ve(&subscriber_ussd_notify_cmd);
+	install_element_ve(&subscriber_update_cmd);
+	install_element_ve(&show_stats_cmd);
+	install_element_ve(&show_smsqueue_cmd);
+
+	install_element(ENABLE_NODE, &ena_subscr_name_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_extension_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_authorized_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_clear_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_pend_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_kick_cmd);
+	install_element(ENABLE_NODE, &subscriber_purge_cmd);
+	install_element(ENABLE_NODE, &smsqueue_trigger_cmd);
+	install_element(ENABLE_NODE, &smsqueue_max_cmd);
+	install_element(ENABLE_NODE, &smsqueue_clear_cmd);
+	install_element(ENABLE_NODE, &smsqueue_fail_cmd);
+	install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd);
+
+	return 0;
+}
diff --git a/src/libtrau/Makefile.am b/src/libtrau/Makefile.am
new file mode 100644
index 0000000..01ed251
--- /dev/null
+++ b/src/libtrau/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libtrau.a
+
+libtrau_a_SOURCES = rtp_proxy.c subchan_demux.c trau_frame.c trau_mux.c trau_upqueue.c
diff --git a/src/libtrau/Makefile.in b/src/libtrau/Makefile.in
new file mode 100644
index 0000000..9da0496
--- /dev/null
+++ b/src/libtrau/Makefile.in
@@ -0,0 +1,455 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libtrau
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libtrau_a_AR = $(AR) $(ARFLAGS)
+libtrau_a_LIBADD =
+am_libtrau_a_OBJECTS = rtp_proxy.$(OBJEXT) subchan_demux.$(OBJEXT) \
+	trau_frame.$(OBJEXT) trau_mux.$(OBJEXT) trau_upqueue.$(OBJEXT)
+libtrau_a_OBJECTS = $(am_libtrau_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libtrau_a_SOURCES)
+DIST_SOURCES = $(libtrau_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libtrau.a
+libtrau_a_SOURCES = rtp_proxy.c subchan_demux.c trau_frame.c trau_mux.c trau_upqueue.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libtrau/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libtrau/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libtrau.a: $(libtrau_a_OBJECTS) $(libtrau_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libtrau.a
+	$(AM_V_AR)$(libtrau_a_AR) libtrau.a $(libtrau_a_OBJECTS) $(libtrau_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libtrau.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rtp_proxy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subchan_demux.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trau_frame.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trau_mux.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trau_upqueue.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libtrau/rtp_proxy.c b/src/libtrau/rtp_proxy.c
new file mode 100644
index 0000000..eefc0e1
--- /dev/null
+++ b/src/libtrau/rtp_proxy.c
@@ -0,0 +1,728 @@
+/* RTP proxy handling for ip.access nanoBTS */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>    /* gettimeofday() */
+#include <unistd.h>      /* get..() */
+#include <time.h>        /* clock() */
+#include <sys/utsname.h> /* uname() */
+
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/msgb.h>
+#include <osmocore/select.h>
+#include <openbsc/debug.h>
+#include <openbsc/rtp_proxy.h>
+
+/* attempt to determine byte order */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <limits.h>
+
+#ifndef __BYTE_ORDER
+#error "__BYTE_ORDER should be defined by someone"
+#endif
+
+static LLIST_HEAD(rtp_sockets);
+
+/* should we mangle the CNAME inside SDES of RTCP packets? We disable
+ * this by default, as it seems to be not needed */
+static int mangle_rtcp_cname = 0;
+
+enum rtp_bfd_priv {
+	RTP_PRIV_NONE,
+	RTP_PRIV_RTP,
+	RTP_PRIV_RTCP
+};
+
+#define RTP_ALLOC_SIZE	1500
+
+/* according to RFC 1889 */
+struct rtcp_hdr {
+	u_int8_t byte0;
+	u_int8_t type;
+	u_int16_t length;
+} __attribute__((packed));
+
+#define RTCP_TYPE_SDES	202
+	
+#define RTCP_IE_CNAME	1
+
+/* according to RFC 3550 */
+struct rtp_hdr {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	u_int8_t  csrc_count:4,
+		  extension:1,
+		  padding:1,
+		  version:2;
+	u_int8_t  payload_type:7,
+		  marker:1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	u_int8_t  version:2,
+		  padding:1,
+		  extension:1,
+		  csrc_count:4;
+	u_int8_t  marker:1,
+		  payload_type:7;
+#endif
+	u_int16_t sequence;
+	u_int32_t timestamp;
+	u_int32_t ssrc;
+} __attribute__((packed));
+
+struct rtp_x_hdr {
+	u_int16_t by_profile;
+	u_int16_t length;
+} __attribute__((packed));
+
+#define RTP_VERSION	2
+
+/* decode an rtp frame and create a new buffer with payload */
+static int rtp_decode(struct msgb *msg, u_int32_t callref, struct msgb **data)
+{
+	struct msgb *new_msg;
+	struct gsm_data_frame *frame;
+	struct rtp_hdr *rtph = (struct rtp_hdr *)msg->data;
+	struct rtp_x_hdr *rtpxh;
+	u_int8_t *payload;
+	int payload_len;
+	int msg_type;
+	int x_len;
+
+	if (msg->len < 12) {
+		DEBUGPC(DMUX, "received RTP frame too short (len = %d)\n",
+			msg->len);
+		return -EINVAL;
+	}
+	if (rtph->version != RTP_VERSION) {
+		DEBUGPC(DMUX, "received RTP version %d not supported.\n",
+			rtph->version);
+		return -EINVAL;
+	}
+	payload = msg->data + sizeof(struct rtp_hdr) + (rtph->csrc_count << 2);
+	payload_len = msg->len - sizeof(struct rtp_hdr) - (rtph->csrc_count << 2);
+	if (payload_len < 0) {
+		DEBUGPC(DMUX, "received RTP frame too short (len = %d, "
+			"csrc count = %d)\n", msg->len, rtph->csrc_count);
+		return -EINVAL;
+	}
+	if (rtph->extension) {
+		if (payload_len < sizeof(struct rtp_x_hdr)) {
+			DEBUGPC(DMUX, "received RTP frame too short for "
+				"extension header\n");
+			return -EINVAL;
+		}
+		rtpxh = (struct rtp_x_hdr *)payload;
+		x_len = ntohs(rtpxh->length) * 4 + sizeof(struct rtp_x_hdr);
+		payload += x_len;
+		payload_len -= x_len;
+		if (payload_len < 0) {
+			DEBUGPC(DMUX, "received RTP frame too short, "
+				"extension header exceeds frame length\n");
+			return -EINVAL;
+		}
+	}
+	if (rtph->padding) {
+		if (payload_len < 0) {
+			DEBUGPC(DMUX, "received RTP frame too short for "
+				"padding length\n");
+			return -EINVAL;
+		}
+		payload_len -= payload[payload_len - 1];
+		if (payload_len < 0) {
+			DEBUGPC(DMUX, "received RTP frame with padding "
+				"greater than payload\n");
+			return -EINVAL;
+		}
+	}
+
+	switch (rtph->payload_type) {
+	case RTP_PT_GSM_FULL:
+		msg_type = GSM_TCHF_FRAME;
+		if (payload_len != 33) {
+			DEBUGPC(DMUX, "received RTP full rate frame with "
+				"payload length != 32 (len = %d)\n",
+				payload_len);
+			return -EINVAL;
+		}
+		break;
+	case RTP_PT_GSM_EFR:
+		msg_type = GSM_TCHF_FRAME_EFR;
+		break;
+	default:
+		DEBUGPC(DMUX, "received RTP frame with unknown payload "
+			"type %d\n", rtph->payload_type);
+		return -EINVAL;
+	}
+
+	new_msg = msgb_alloc(sizeof(struct gsm_data_frame) + payload_len,
+				"GSM-DATA");
+	if (!new_msg)
+		return -ENOMEM;
+	frame = (struct gsm_data_frame *)(new_msg->data);
+	frame->msg_type = msg_type;
+	frame->callref = callref;
+	memcpy(frame->data, payload, payload_len);
+	msgb_put(new_msg, sizeof(struct gsm_data_frame) + payload_len);
+
+	*data = new_msg;
+	return 0;
+}
+
+/* "to - from" */
+static void tv_difference(struct timeval *diff, const struct timeval *from,
+			  const struct timeval *__to)
+{
+	struct timeval _to = *__to, *to = &_to;
+
+	if (to->tv_usec < from->tv_usec) {
+		to->tv_sec -= 1;
+		to->tv_usec += 1000000;
+	}
+
+	diff->tv_usec = to->tv_usec - from->tv_usec;
+	diff->tv_sec = to->tv_sec - from->tv_sec;
+}
+
+/* encode and send a rtp frame */
+int rtp_send_frame(struct rtp_socket *rs, struct gsm_data_frame *frame)
+{
+	struct rtp_sub_socket *rss = &rs->rtp;
+	struct msgb *msg;
+	struct rtp_hdr *rtph;
+	int payload_type;
+	int payload_len;
+	int duration; /* in samples */
+
+	if (rs->tx_action != RTP_SEND_DOWNSTREAM) {
+		/* initialize sequences */
+		rs->tx_action = RTP_SEND_DOWNSTREAM;
+		rs->transmit.ssrc = rand();
+		rs->transmit.sequence = random();
+		rs->transmit.timestamp = random();
+	}
+
+	switch (frame->msg_type) {
+	case GSM_TCHF_FRAME:
+		payload_type = RTP_PT_GSM_FULL;
+		payload_len = 33;
+		duration = 160;
+		break;
+	case GSM_TCHF_FRAME_EFR:
+		payload_type = RTP_PT_GSM_EFR;
+		payload_len = 31;
+		duration = 160;
+		break;
+	default:
+		DEBUGPC(DMUX, "unsupported message type %d\n",
+			frame->msg_type);
+		return -EINVAL;
+	}
+
+	{
+		struct timeval tv, tv_diff;
+		long int usec_diff, frame_diff;
+
+		gettimeofday(&tv, NULL);
+		tv_difference(&tv_diff, &rs->transmit.last_tv, &tv);
+		rs->transmit.last_tv = tv;
+
+		usec_diff = tv_diff.tv_sec * 1000000 + tv_diff.tv_usec;
+		frame_diff = (usec_diff / 20000);
+
+		if (abs(frame_diff) > 1) {
+			long int frame_diff_excess = frame_diff - 1;
+
+			LOGP(DMUX, LOGL_NOTICE,
+				"Correcting frame difference of %ld frames\n", frame_diff_excess);
+			rs->transmit.sequence += frame_diff_excess;
+			rs->transmit.timestamp += frame_diff_excess * duration;
+		}
+	}
+
+	msg = msgb_alloc(sizeof(struct rtp_hdr) + payload_len, "RTP-GSM-FULL");
+	if (!msg)
+		return -ENOMEM;
+	rtph = (struct rtp_hdr *)msg->data;
+	rtph->version = RTP_VERSION;
+	rtph->padding = 0;
+	rtph->extension = 0;
+	rtph->csrc_count = 0;
+	rtph->marker = 0;
+	rtph->payload_type = payload_type;
+	rtph->sequence = htons(rs->transmit.sequence++);
+	rtph->timestamp = htonl(rs->transmit.timestamp);
+	rs->transmit.timestamp += duration;
+	rtph->ssrc = htonl(rs->transmit.ssrc);
+	memcpy(msg->data + sizeof(struct rtp_hdr), frame->data, payload_len);
+	msgb_put(msg, sizeof(struct rtp_hdr) + payload_len);
+	msgb_enqueue(&rss->tx_queue, msg);
+	rss->bfd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+/* iterate over all chunks in one RTCP message, look for CNAME IEs and
+ * replace all of those with 'new_cname' */
+static int rtcp_sdes_cname_mangle(struct msgb *msg, struct rtcp_hdr *rh,
+				  u_int16_t *rtcp_len, const char *new_cname)
+{
+	u_int8_t *rtcp_end;
+	u_int8_t *cur = (u_int8_t *) rh;
+	u_int8_t tag, len = 0;
+
+	rtcp_end = cur + *rtcp_len;
+	/* move cur to end of RTP header */
+	cur += sizeof(*rh);
+
+	/* iterate over Chunks */
+	while (cur+4 < rtcp_end) {
+		/* skip four bytes SSRC/CSRC */
+		cur += 4;
+	
+		/* iterate over IE's inside the chunk */
+		while (cur+1 < rtcp_end) {
+			tag = *cur++;
+			if (tag == 0) {
+				/* end of chunk, skip additional zero */
+				while (*cur++ == 0) { }
+				break;
+			}
+			len = *cur++;
+	
+			if (tag == RTCP_IE_CNAME) {
+				/* we've found the CNAME, lets mangle it */
+				if (len < strlen(new_cname)) {
+					/* we need to make more space */
+					int increase = strlen(new_cname) - len;
+
+					msgb_push(msg, increase);
+					memmove(cur+len+increase, cur+len,
+						rtcp_end - (cur+len));
+					/* FIXME: we have to respect RTCP
+					 * padding/alignment rules! */
+					len += increase;
+					*(cur-1) += increase;
+					rtcp_end += increase;
+					*rtcp_len += increase;
+				}
+				/* copy new CNAME into message */
+				memcpy(cur, new_cname, strlen(new_cname));
+				/* FIXME: zero the padding in case new CNAME
+				 * is smaller than old one !!! */
+			}
+			cur += len;
+		}
+	}
+
+	return 0;
+}
+
+static int rtcp_mangle(struct msgb *msg, struct rtp_socket *rs)
+{
+	struct rtp_sub_socket *rss = &rs->rtcp;
+	struct rtcp_hdr *rtph;
+	u_int16_t old_len;
+	int rc;
+
+	if (!mangle_rtcp_cname)
+		return 0;
+
+	printf("RTCP\n");
+	/* iterate over list of RTCP messages */
+	rtph = (struct rtcp_hdr *)msg->data;
+	while ((void *)rtph + sizeof(*rtph) <= (void *)msg->data + msg->len) {
+		old_len = (ntohs(rtph->length) + 1) * 4;
+		if ((void *)rtph + old_len > (void *)msg->data + msg->len) {
+			DEBUGPC(DMUX, "received RTCP packet too short for "
+				"length element\n");
+			return -EINVAL;
+		}
+		if (rtph->type == RTCP_TYPE_SDES) {
+			char new_cname[255];
+			strncpy(new_cname, inet_ntoa(rss->sin_local.sin_addr),
+				sizeof(new_cname));
+			new_cname[sizeof(new_cname)-1] = '\0';
+			rc = rtcp_sdes_cname_mangle(msg, rtph, &old_len,
+						    new_cname);
+			if (rc < 0)
+				return rc;
+		}
+		rtph = (void *)rtph + old_len;
+	}
+
+	return 0;
+}
+
+/* read from incoming RTP/RTCP socket */
+static int rtp_socket_read(struct rtp_socket *rs, struct rtp_sub_socket *rss)
+{
+	int rc;
+	struct msgb *msg = msgb_alloc(RTP_ALLOC_SIZE, "RTP/RTCP");
+	struct msgb *new_msg;
+	struct rtp_sub_socket *other_rss;
+
+	if (!msg)
+		return -ENOMEM;
+
+	rc = read(rss->bfd.fd, msg->data, RTP_ALLOC_SIZE);
+	if (rc <= 0) {
+		rss->bfd.when &= ~BSC_FD_READ;
+		return rc;
+	}
+
+	msgb_put(msg, rc);
+
+	switch (rs->rx_action) {
+	case RTP_PROXY:
+		if (!rs->proxy.other_sock) {
+			rc = -EIO;
+			goto out_free;
+		}
+		if (rss->bfd.priv_nr == RTP_PRIV_RTP)
+			other_rss = &rs->proxy.other_sock->rtp;
+		else if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
+			other_rss = &rs->proxy.other_sock->rtcp;
+			/* modify RTCP SDES CNAME */
+			rc = rtcp_mangle(msg, rs);
+			if (rc < 0)
+				goto out_free;
+		} else {
+			rc = -EINVAL;
+			goto out_free;
+		}
+		msgb_enqueue(&other_rss->tx_queue, msg);
+		other_rss->bfd.when |= BSC_FD_WRITE;
+		break;
+
+	case RTP_RECV_UPSTREAM:
+		if (!rs->receive.callref || !rs->receive.net) {
+			rc = -EIO;
+			goto out_free;
+		}
+		if (rss->bfd.priv_nr == RTP_PRIV_RTCP) {
+			if (!mangle_rtcp_cname) {
+				msgb_free(msg);
+				break;
+			}
+			/* modify RTCP SDES CNAME */
+			rc = rtcp_mangle(msg, rs);
+			if (rc < 0)
+				goto out_free;
+			msgb_enqueue(&rss->tx_queue, msg);
+			rss->bfd.when |= BSC_FD_WRITE;
+			break;
+		}
+		if (rss->bfd.priv_nr != RTP_PRIV_RTP) {
+			rc = -EINVAL;
+			goto out_free;
+		}
+		rc = rtp_decode(msg, rs->receive.callref, &new_msg);
+		if (rc < 0)
+			goto out_free;
+		msgb_free(msg);
+		trau_tx_to_mncc(rs->receive.net, new_msg);
+		break;
+
+	case RTP_NONE: /* if socket exists, but disabled by app */
+		msgb_free(msg);
+		break;
+	}
+
+	return 0;
+
+out_free:
+	msgb_free(msg);
+	return rc;
+}
+
+/* write from tx_queue to RTP/RTCP socket */
+static int rtp_socket_write(struct rtp_socket *rs, struct rtp_sub_socket *rss)
+{
+	struct msgb *msg;
+	int written;
+
+	msg = msgb_dequeue(&rss->tx_queue);
+	if (!msg) {
+		rss->bfd.when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+
+	written = write(rss->bfd.fd, msg->data, msg->len);
+	if (written < msg->len) {
+		LOGP(DMIB, LOGL_ERROR, "short write");
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	msgb_free(msg);
+
+	return 0;
+}
+
+
+/* callback for the select.c:bfd_* layer */
+static int rtp_bfd_cb(struct bsc_fd *bfd, unsigned int flags)
+{
+	struct rtp_socket *rs = bfd->data;
+	struct rtp_sub_socket *rss;
+
+	switch (bfd->priv_nr) {
+	case RTP_PRIV_RTP:
+		rss = &rs->rtp;
+		break;
+	case RTP_PRIV_RTCP:
+		rss = &rs->rtcp;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (flags & BSC_FD_READ)
+		rtp_socket_read(rs, rss);
+
+	if (flags & BSC_FD_WRITE)
+		rtp_socket_write(rs, rss);
+
+	return 0;
+}
+
+static void init_rss(struct rtp_sub_socket *rss,
+		     struct rtp_socket *rs, int fd, int priv_nr)
+{
+	/* initialize bfd */
+	rss->bfd.fd = fd;
+	rss->bfd.data = rs;
+	rss->bfd.priv_nr = priv_nr;
+	rss->bfd.cb = rtp_bfd_cb;
+}
+
+struct rtp_socket *rtp_socket_create(void)
+{
+	int rc;
+	struct rtp_socket *rs;
+
+	DEBUGP(DMUX, "rtp_socket_create(): ");
+
+	rs = talloc_zero(tall_bsc_ctx, struct rtp_socket);
+	if (!rs)
+		return NULL;
+
+	INIT_LLIST_HEAD(&rs->rtp.tx_queue);
+	INIT_LLIST_HEAD(&rs->rtcp.tx_queue);
+
+	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (rc < 0)
+		goto out_free;
+
+	init_rss(&rs->rtp, rs, rc, RTP_PRIV_RTP);
+	rc = bsc_register_fd(&rs->rtp.bfd);
+	if (rc < 0)
+		goto out_rtp_socket;
+
+	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (rc < 0)
+		goto out_rtp_bfd;
+
+	init_rss(&rs->rtcp, rs, rc, RTP_PRIV_RTCP);
+	rc = bsc_register_fd(&rs->rtcp.bfd);
+	if (rc < 0)
+		goto out_rtcp_socket;
+
+	DEBUGPC(DMUX, "success\n");
+
+	rc = rtp_socket_bind(rs, INADDR_ANY);
+	if (rc < 0)
+		goto out_rtcp_bfd;
+
+	return rs;
+
+out_rtcp_bfd:
+	bsc_unregister_fd(&rs->rtcp.bfd);
+out_rtcp_socket:
+	close(rs->rtcp.bfd.fd);
+out_rtp_bfd:
+	bsc_unregister_fd(&rs->rtp.bfd);
+out_rtp_socket:
+	close(rs->rtp.bfd.fd);
+out_free:
+	talloc_free(rs);
+	DEBUGPC(DMUX, "failed\n");
+	return NULL;
+}
+
+static int rtp_sub_socket_bind(struct rtp_sub_socket *rss, u_int32_t ip,
+				u_int16_t port)
+{
+	int rc;
+	socklen_t alen = sizeof(rss->sin_local);
+
+	rss->sin_local.sin_family = AF_INET;
+	rss->sin_local.sin_addr.s_addr = htonl(ip);
+	rss->sin_local.sin_port = htons(port);
+	rss->bfd.when |= BSC_FD_READ;
+
+	rc = bind(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+		  sizeof(rss->sin_local));
+	if (rc < 0)
+		return rc;
+
+	/* retrieve the address we actually bound to, in case we
+	 * passed INADDR_ANY as IP address */
+	return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+			   &alen);
+}
+
+#define RTP_PORT_BASE	30000
+static unsigned int next_udp_port = RTP_PORT_BASE;
+
+/* bind a RTP socket to a local address */
+int rtp_socket_bind(struct rtp_socket *rs, u_int32_t ip)
+{
+	int rc = -EIO;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DMUX, "rtp_socket_bind(rs=%p, IP=%s): ", rs,
+		inet_ntoa(ia));
+
+	/* try to bind to a consecutive pair of ports */
+	for (next_udp_port = next_udp_port % 0xffff;
+	     next_udp_port < 0xffff; next_udp_port += 2) {
+		rc = rtp_sub_socket_bind(&rs->rtp, ip, next_udp_port);
+		if (rc != 0)
+			continue;
+
+		rc = rtp_sub_socket_bind(&rs->rtcp, ip, next_udp_port+1);
+		if (rc == 0)
+			break;
+	}
+	if (rc < 0) {
+		DEBUGPC(DMUX, "failed\n");
+		return rc;
+	}
+
+	ia.s_addr = rs->rtp.sin_local.sin_addr.s_addr;
+	DEBUGPC(DMUX, "BOUND_IP=%s, BOUND_PORT=%u\n",
+		inet_ntoa(ia), ntohs(rs->rtp.sin_local.sin_port));
+	return ntohs(rs->rtp.sin_local.sin_port);
+}
+
+static int rtp_sub_socket_connect(struct rtp_sub_socket *rss,
+				  u_int32_t ip, u_int16_t port)
+{
+	int rc;
+	socklen_t alen = sizeof(rss->sin_local);
+
+	rss->sin_remote.sin_family = AF_INET;
+	rss->sin_remote.sin_addr.s_addr = htonl(ip);
+	rss->sin_remote.sin_port = htons(port);
+
+	rc = connect(rss->bfd.fd, (struct sockaddr *) &rss->sin_remote,
+		     sizeof(rss->sin_remote));
+	if (rc < 0)
+		return rc;
+
+	return getsockname(rss->bfd.fd, (struct sockaddr *)&rss->sin_local,
+			   &alen);
+}
+
+/* 'connect' a RTP socket to a remote peer */
+int rtp_socket_connect(struct rtp_socket *rs, u_int32_t ip, u_int16_t port)
+{
+	int rc;
+	struct in_addr ia;
+
+	ia.s_addr = htonl(ip);
+	DEBUGP(DMUX, "rtp_socket_connect(rs=%p, ip=%s, port=%u)\n",
+		rs, inet_ntoa(ia), port);
+
+	rc = rtp_sub_socket_connect(&rs->rtp, ip, port);
+	if (rc < 0)
+		return rc;
+
+	return rtp_sub_socket_connect(&rs->rtcp, ip, port+1);
+}
+
+/* bind two RTP/RTCP sockets together */
+int rtp_socket_proxy(struct rtp_socket *this, struct rtp_socket *other)
+{
+	DEBUGP(DMUX, "rtp_socket_proxy(this=%p, other=%p)\n",
+		this, other);
+
+	this->rx_action = RTP_PROXY;
+	this->proxy.other_sock = other;
+
+	other->rx_action = RTP_PROXY;
+	other->proxy.other_sock = this;
+
+	return 0;
+}
+
+/* bind RTP/RTCP socket to application */
+int rtp_socket_upstream(struct rtp_socket *this, struct gsm_network *net,
+			u_int32_t callref)
+{
+	DEBUGP(DMUX, "rtp_socket_proxy(this=%p, callref=%u)\n",
+		this, callref);
+
+	if (callref) {
+		this->rx_action = RTP_RECV_UPSTREAM;
+		this->receive.net = net;
+		this->receive.callref = callref;
+	} else
+		this->rx_action = RTP_NONE;
+
+	return 0;
+}
+
+static void free_tx_queue(struct rtp_sub_socket *rss)
+{
+	struct msgb *msg;
+	
+	while ((msg = msgb_dequeue(&rss->tx_queue)))
+		msgb_free(msg);
+}
+
+int rtp_socket_free(struct rtp_socket *rs)
+{
+	DEBUGP(DMUX, "rtp_socket_free(rs=%p)\n", rs);
+
+	/* make sure we don't leave references dangling to us */
+	if (rs->rx_action == RTP_PROXY &&
+	    rs->proxy.other_sock)
+		rs->proxy.other_sock->proxy.other_sock = NULL;
+
+	bsc_unregister_fd(&rs->rtp.bfd);
+	close(rs->rtp.bfd.fd);
+	free_tx_queue(&rs->rtp);
+
+	bsc_unregister_fd(&rs->rtcp.bfd);
+	close(rs->rtcp.bfd.fd);
+	free_tx_queue(&rs->rtcp);
+
+	talloc_free(rs);
+
+	return 0;
+}
diff --git a/src/libtrau/subchan_demux.c b/src/libtrau/subchan_demux.c
new file mode 100644
index 0000000..6bcf279
--- /dev/null
+++ b/src/libtrau/subchan_demux.c
@@ -0,0 +1,321 @@
+/* A E1 sub-channel (de)multiplexer with TRAU frame sync */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/subchan_demux.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+
+void *tall_tqe_ctx;
+
+static inline void append_bit(struct demux_subch *sch, u_int8_t bit)
+{
+	sch->out_bitbuf[sch->out_idx++] = bit;
+}
+
+#define SYNC_HDR_BITS	16
+static const u_int8_t nullbytes[SYNC_HDR_BITS];
+
+/* check if we have just completed the 16 bit zero sync header,
+ * in accordance with GSM TS 08.60 Chapter 4.8.1 */
+static int sync_hdr_complete(struct demux_subch *sch, u_int8_t bit)
+{
+	if (bit == 0)
+		sch->consecutive_zeros++;
+	else
+		sch->consecutive_zeros = 0;
+
+	if (sch->consecutive_zeros >= SYNC_HDR_BITS) {
+		sch->consecutive_zeros = 0;
+		return 1;
+	}
+
+	return 0;
+}
+
+/* resynchronize to current location */
+static void resync_to_here(struct demux_subch *sch)
+{
+	memset(sch->out_bitbuf, 0, SYNC_HDR_BITS);
+
+	/* set index in a way that we can continue receiving bits after
+	 * the end of the SYNC header */
+	sch->out_idx = SYNC_HDR_BITS;
+	sch->in_sync = 1;
+}
+
+int subch_demux_init(struct subch_demux *dmx)
+{
+	int i;
+
+	dmx->chan_activ = 0;
+	for (i = 0; i < NR_SUBCH; i++) {
+		struct demux_subch *sch = &dmx->subch[i];
+		sch->out_idx = 0;
+		memset(sch->out_bitbuf, 0xff, sizeof(sch->out_bitbuf));
+	}
+	return 0;
+}
+
+/* input some arbitrary (modulo 4) number of bytes of a 64k E1 channel,
+ * split it into the 16k subchannels */
+int subch_demux_in(struct subch_demux *dmx, u_int8_t *data, int len)
+{
+	int i, c;
+
+	/* we avoid partially filled bytes in outbuf */
+	if (len % 4)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		u_int8_t inbyte = data[i];
+
+		for (c = 0; c < NR_SUBCH; c++) {
+			struct demux_subch *sch = &dmx->subch[c];
+			u_int8_t inbits;
+			u_int8_t bit;
+
+			/* ignore inactive subchannels */
+			if (!(dmx->chan_activ & (1 << c)))
+				continue;
+
+			inbits = inbyte >> (c << 1);
+
+			/* two bits for each subchannel */
+			if (inbits & 0x01)
+				bit = 1;
+			else
+				bit = 0;
+			append_bit(sch, bit);
+
+			if (sync_hdr_complete(sch, bit))
+				resync_to_here(sch);
+
+			if (inbits & 0x02)
+				bit = 1;
+			else
+				bit = 0;
+			append_bit(sch, bit);
+
+			if (sync_hdr_complete(sch, bit))
+				resync_to_here(sch);
+
+			/* FIXME: verify the first bit in octet 2, 4, 6, ...
+			 * according to TS 08.60 4.8.1 */
+
+			/* once we have reached TRAU_FRAME_BITS, call
+			 * the TRAU frame handler callback function */
+			if (sch->out_idx >= TRAU_FRAME_BITS) {
+				if (sch->in_sync) {
+					dmx->out_cb(dmx, c, sch->out_bitbuf,
+					    sch->out_idx, dmx->data);
+					sch->in_sync = 0;
+				}
+				sch->out_idx = 0;
+			}
+		}
+	}
+	return i;
+}
+
+int subch_demux_activate(struct subch_demux *dmx, int subch)
+{
+	if (subch >= NR_SUBCH)
+		return -EINVAL;
+
+	dmx->chan_activ |= (1 << subch);
+	return 0;
+}
+
+int subch_demux_deactivate(struct subch_demux *dmx, int subch)
+{
+	if (subch >= NR_SUBCH)
+		return -EINVAL;
+
+	dmx->chan_activ &= ~(1 << subch);
+	return 0;
+}
+
+/* MULTIPLEXER */
+
+static int alloc_add_idle_frame(struct subch_mux *mx, int sch_nr)
+{
+	/* allocate and initialize with idle pattern */
+	return subchan_mux_enqueue(mx, sch_nr, trau_idle_frame(),
+				   TRAU_FRAME_BITS);
+}
+
+/* return the requested number of bits from the specified subchannel */
+static int get_subch_bits(struct subch_mux *mx, int subch,
+			  u_int8_t *bits, int num_requested)
+{
+	struct mux_subch *sch = &mx->subch[subch];
+	int num_bits = 0;
+
+	while (num_bits < num_requested) {
+		struct subch_txq_entry *txe;
+		int num_bits_left;
+		int num_bits_thistime;
+
+		/* make sure we have a valid entry at top of tx queue.
+		 * if not, add an idle frame */
+		if (llist_empty(&sch->tx_queue))
+			alloc_add_idle_frame(mx, subch);
+	
+		if (llist_empty(&sch->tx_queue))
+			return -EIO;
+
+		txe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list);
+		num_bits_left = txe->bit_len - txe->next_bit;
+
+		if (num_bits_left < num_requested)
+			num_bits_thistime = num_bits_left;
+		else
+			num_bits_thistime = num_requested;
+
+		/* pull the bits from the txe */
+		memcpy(bits + num_bits, txe->bits + txe->next_bit, num_bits_thistime);
+		txe->next_bit += num_bits_thistime;
+
+		/* free the tx_queue entry if it is fully consumed */
+		if (txe->next_bit >= txe->bit_len) {
+			llist_del(&txe->list);
+			talloc_free(txe);
+		}
+
+		/* increment global number of bits dequeued */
+		num_bits += num_bits_thistime;
+	}
+
+	return num_requested;
+}
+
+/* compact an array of 8 single-bit bytes into one byte of 8 bits */
+static u_int8_t compact_bits(const u_int8_t *bits)
+{
+	u_int8_t ret = 0;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		ret |= (bits[i] ? 1 : 0) << i;
+
+	return ret;
+}
+
+/* obtain a single output byte from the subchannel muxer */
+static int mux_output_byte(struct subch_mux *mx, u_int8_t *byte)
+{
+	u_int8_t bits[8];
+	int rc;
+
+	/* combine two bits of every subchan */
+	rc = get_subch_bits(mx, 0, &bits[0], 2);
+	rc = get_subch_bits(mx, 1, &bits[2], 2);
+	rc = get_subch_bits(mx, 2, &bits[4], 2);
+	rc = get_subch_bits(mx, 3, &bits[6], 2);
+
+	*byte = compact_bits(bits);
+
+	return rc;
+}
+
+/* Request the output of some muxed bytes from the subchan muxer */
+int subchan_mux_out(struct subch_mux *mx, u_int8_t *data, int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		int rc;
+		rc = mux_output_byte(mx, &data[i]);
+		if (rc < 0)
+			break;
+	}
+	return i;
+}
+
+static int llist_len(struct llist_head *head)
+{
+	struct llist_head *entry;
+	int i = 0;
+
+	llist_for_each(entry, head)
+		i++;
+
+	return i;
+}
+
+/* evict the 'num_evict' number of oldest entries in the queue */
+static void tx_queue_evict(struct mux_subch *sch, int num_evict)
+{
+	struct subch_txq_entry *tqe;
+	int i;
+
+	for (i = 0; i < num_evict; i++) {
+		if (llist_empty(&sch->tx_queue))
+			return;
+
+		tqe = llist_entry(sch->tx_queue.next, struct subch_txq_entry, list);
+		llist_del(&tqe->list);
+		talloc_free(tqe);
+	}
+}
+
+/* enqueue some data into the tx_queue of a given subchannel */
+int subchan_mux_enqueue(struct subch_mux *mx, int s_nr, const u_int8_t *data,
+			int len)
+{
+	struct mux_subch *sch = &mx->subch[s_nr];
+	int list_len = llist_len(&sch->tx_queue);
+	struct subch_txq_entry *tqe = talloc_zero_size(tall_tqe_ctx,
+							sizeof(*tqe) + len);
+	if (!tqe)
+		return -ENOMEM;
+
+	tqe->bit_len = len;
+	memcpy(tqe->bits, data, len);
+
+	if (list_len > 2)
+		tx_queue_evict(sch, list_len-2);
+
+	llist_add_tail(&tqe->list, &sch->tx_queue);
+
+	return 0;
+}
+
+/* initialize one subchannel muxer instance */
+int subchan_mux_init(struct subch_mux *mx)
+{
+	int i;
+
+	memset(mx, 0, sizeof(*mx));
+	for (i = 0; i < NR_SUBCH; i++) {
+		struct mux_subch *sch = &mx->subch[i];
+		INIT_LLIST_HEAD(&sch->tx_queue);
+	}
+
+	return 0;
+}
diff --git a/src/libtrau/trau_frame.c b/src/libtrau/trau_frame.c
new file mode 100644
index 0000000..d4d6447
--- /dev/null
+++ b/src/libtrau/trau_frame.c
@@ -0,0 +1,260 @@
+/* TRAU frame handling according to GSM TS 08.60 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/trau_frame.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/debug.h>
+
+static u_int32_t get_bits(const u_int8_t *bitbuf, int offset, int num)
+{
+	int i;
+	u_int32_t ret = 0;
+
+	for (i = offset; i < offset + num; i++) {
+		ret = ret << 1;
+		if (bitbuf[i])
+			ret |= 1;
+	}
+	return ret;
+}
+
+/* Decode according to 3.1.1 */
+static void decode_fr(struct decoded_trau_frame *fr, const u_int8_t *trau_bits)
+{
+	int i;
+	int d_idx = 0;
+
+	/* C1 .. C15 */
+	memcpy(fr->c_bits+0, trau_bits+17, 15);
+	/* C16 .. C21 */
+	memcpy(fr->c_bits+15, trau_bits+310, 6);
+	/* T1 .. T4 */
+	memcpy(fr->t_bits+0, trau_bits+316, 4);
+	/* D1 .. D255 */
+	for (i = 32; i < 304; i+= 16) {
+		memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15);
+		d_idx += 15;
+	}
+	/* D256 .. D260 */
+	memcpy(fr->d_bits + d_idx, trau_bits + 305, 5);
+}
+
+/* Decode according to 3.1.2 */
+static void decode_amr(struct decoded_trau_frame *fr, const u_int8_t *trau_bits)
+{
+	int i;
+	int d_idx = 0;
+
+	/* C1 .. C15 */
+	memcpy(fr->c_bits+0, trau_bits+17, 15);
+	/* C16 .. C25 */
+	memcpy(fr->c_bits+15, trau_bits+33, 10);
+	/* T1 .. T4 */
+	memcpy(fr->t_bits+0, trau_bits+316, 4);
+	/* D1 .. D5 */
+	memcpy(fr->d_bits, trau_bits+43, 5);
+	/* D6 .. D245 */
+	for (i = 48; i < 304; i += 16) {
+		memcpy(fr->d_bits + d_idx, trau_bits+i+1, 15);
+		d_idx += 15;
+	}
+	/* D246 .. D256 */
+	memcpy(fr->d_bits + d_idx, trau_bits + 305, 11);
+}
+
+int decode_trau_frame(struct decoded_trau_frame *fr, const u_int8_t *trau_bits)
+{
+	u_int8_t cbits5 = get_bits(trau_bits, 17, 5);
+
+	switch (cbits5) {
+	case TRAU_FT_FR_UP:
+	case TRAU_FT_FR_DOWN:
+	case TRAU_FT_IDLE_UP:
+	case TRAU_FT_IDLE_DOWN:
+	case TRAU_FT_EFR:
+		decode_fr(fr, trau_bits);
+		break;
+	case TRAU_FT_AMR:
+		decode_amr(fr, trau_bits);
+		break;
+	case TRAU_FT_OM_UP:
+	case TRAU_FT_OM_DOWN:
+	case TRAU_FT_DATA_UP:
+	case TRAU_FT_DATA_DOWN:
+	case TRAU_FT_D145_SYNC:
+	case TRAU_FT_EDATA:
+		LOGP(DMUX, LOGL_NOTICE, "can't decode unimplemented TRAU "
+			"Frame Type 0x%02x\n", cbits5);
+		return -1;
+		break;
+	default:
+		LOGP(DMUX, LOGL_NOTICE, "can't decode unknown TRAU "
+			"Frame Type 0x%02x\n", cbits5);
+		return -1;
+		break;
+	}
+
+	return 0;
+}
+
+const u_int8_t ft_fr_down_bits[] = { 1, 1, 1, 0, 0 };
+const u_int8_t ft_idle_down_bits[] = { 0, 1, 1, 1, 0 };
+
+/* modify an uplink TRAU frame so we can send it downlink */
+int trau_frame_up2down(struct decoded_trau_frame *fr)
+{
+	u_int8_t cbits5 = get_bits(fr->c_bits, 0, 5);
+
+	switch (cbits5) {
+	case TRAU_FT_FR_UP:
+		memcpy(fr->c_bits, ft_fr_down_bits, 5);
+		/* clear time alignment */
+		memset(fr->c_bits+5, 0, 6);
+		/* FIXME: SP / BFI in case of DTx */
+		/* C12 .. C21 are spare and coded as '1' */
+		memset(fr->c_bits+11, 0x01, 10);
+		break;
+	case TRAU_FT_EFR:
+		/* clear time alignment */
+		memset(fr->c_bits+5, 0, 6);
+		/* FIXME: set UFE appropriately */
+		/* FIXME: SP / BFI in case of DTx */
+		break;
+	case TRAU_FT_IDLE_UP:
+		memcpy(fr->c_bits, ft_idle_down_bits, 5);
+		/* clear time alignment */
+		memset(fr->c_bits+5, 0, 6);
+		/* FIXME: SP / BFI in case of DTx */
+		/* C12 .. C21 are spare and coded as '1' */
+		memset(fr->c_bits+11, 0x01, 10);
+		break;
+	case TRAU_FT_FR_DOWN:
+	case TRAU_FT_IDLE_DOWN:
+	case TRAU_FT_OM_DOWN:
+	case TRAU_FT_DATA_DOWN:
+		/* we cannot convert a downlink to a downlink frame */
+		return -EINVAL;
+		break;
+	case TRAU_FT_AMR:
+	case TRAU_FT_OM_UP:
+	case TRAU_FT_DATA_UP:
+	case TRAU_FT_D145_SYNC:
+	case TRAU_FT_EDATA:
+		LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	default:
+		LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	}
+
+	return 0;
+
+}
+
+static void encode_fr(u_int8_t *trau_bits, const struct decoded_trau_frame *fr)
+{
+	int i;
+	int d_idx = 0;
+
+	trau_bits[16] = 1;
+	/* C1 .. C15 */
+	memcpy(trau_bits+17, fr->c_bits+0, 15);
+	/* D1 .. D255 */
+	for (i = 32; i < 304; i+= 16) {
+		trau_bits[i] = 1;
+		memcpy(trau_bits+i+1, fr->d_bits + d_idx, 15);
+		d_idx += 15;
+	}
+	/* D256 .. D260 */
+	trau_bits[304] = 1;
+	memcpy(trau_bits + 305, fr->d_bits + d_idx, 5);
+	/* C16 .. C21 */
+	memcpy(trau_bits+310, fr->c_bits+15, 6);
+
+	/* FIXME: handle timing adjustment */
+
+	/* T1 .. T4 */
+	memcpy(trau_bits+316, fr->t_bits+0, 4);
+}
+
+
+int encode_trau_frame(u_int8_t *trau_bits, const struct decoded_trau_frame *fr)
+{
+	u_int8_t cbits5 = get_bits(fr->c_bits, 0, 5);
+	
+	/* 16 bits of sync header */
+	memset(trau_bits, 0, 16);
+
+	switch (cbits5) {
+	case TRAU_FT_FR_UP:
+	case TRAU_FT_FR_DOWN:
+	case TRAU_FT_IDLE_UP:
+	case TRAU_FT_IDLE_DOWN:
+	case TRAU_FT_EFR:
+		encode_fr(trau_bits, fr);
+		break;
+	case TRAU_FT_AMR:
+	case TRAU_FT_OM_UP:
+	case TRAU_FT_OM_DOWN:
+	case TRAU_FT_DATA_UP:
+	case TRAU_FT_DATA_DOWN:
+	case TRAU_FT_D145_SYNC:
+	case TRAU_FT_EDATA:
+		LOGP(DMUX, LOGL_NOTICE, "unimplemented TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	default:
+		LOGP(DMUX, LOGL_NOTICE, "unknown TRAU Frame Type "
+			"0x%02x\n", cbits5);
+		return -1;
+		break;
+	}
+
+	return 0;
+}
+
+static struct decoded_trau_frame fr_idle_frame = {
+	.c_bits = { 0, 1, 1, 1, 0 },	/* IDLE DOWNLINK 3.5.5 */
+	.t_bits = { 1, 1, 1, 1 },
+};
+static u_int8_t encoded_idle_frame[TRAU_FRAME_BITS];
+static int dbits_initted;
+
+u_int8_t *trau_idle_frame(void)
+{
+	/* only initialize during the first call */
+	if (!dbits_initted) {
+		/* set all D-bits to 1 */
+		memset(&fr_idle_frame.d_bits, 0x01, 260);
+		encode_fr(encoded_idle_frame, &fr_idle_frame);
+	}
+	return encoded_idle_frame;
+}
diff --git a/src/libtrau/trau_mux.c b/src/libtrau/trau_mux.c
new file mode 100644
index 0000000..712e22d
--- /dev/null
+++ b/src/libtrau/trau_mux.c
@@ -0,0 +1,312 @@
+/* Simple TRAU frame reflector to route voice calls */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/subchan_demux.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+
+u_int8_t gsm_fr_map[] = {
+	6, 6, 5, 5, 4, 4, 3, 3,
+	7, 2, 2, 6, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 7, 2, 2, 6, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 7, 2, 2, 6, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 7, 2, 2, 6, 3,
+	3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3
+};
+
+struct map_entry {
+	struct llist_head list;
+	struct gsm_e1_subslot src, dst;
+};
+
+struct upqueue_entry {
+	struct llist_head list;
+	struct gsm_network *net;
+	struct gsm_e1_subslot src;
+	u_int32_t callref;
+};
+
+static LLIST_HEAD(ss_map);
+static LLIST_HEAD(ss_upqueue);
+
+void *tall_map_ctx, *tall_upq_ctx;
+
+/* map one particular subslot to another subslot */
+int trau_mux_map(const struct gsm_e1_subslot *src,
+		 const struct gsm_e1_subslot *dst)
+{
+	struct map_entry *me;
+
+	me = talloc(tall_map_ctx, struct map_entry);
+	if (!me) {
+		LOGP(DMIB, LOGL_FATAL, "Out of memory\n");
+		return -ENOMEM;
+	}
+
+	DEBUGP(DCC, "Setting up TRAU mux map between (e1=%u,ts=%u,ss=%u) "
+		"and (e1=%u,ts=%u,ss=%u)\n",
+		src->e1_nr, src->e1_ts, src->e1_ts_ss,
+		dst->e1_nr, dst->e1_ts, dst->e1_ts_ss);
+
+	/* make sure to get rid of any stale old mappings */
+	trau_mux_unmap(src, 0);
+	trau_mux_unmap(dst, 0);
+
+	memcpy(&me->src, src, sizeof(me->src));
+	memcpy(&me->dst, dst, sizeof(me->dst));
+	llist_add(&me->list, &ss_map);
+
+	return 0;
+}
+
+int trau_mux_map_lchan(const struct gsm_lchan *src,	
+			const struct gsm_lchan *dst)
+{
+	struct gsm_e1_subslot *src_ss, *dst_ss;
+
+	src_ss = &src->ts->e1_link;
+	dst_ss = &dst->ts->e1_link;
+
+	return trau_mux_map(src_ss, dst_ss);
+}
+
+
+/* unmap one particular subslot from another subslot */
+int trau_mux_unmap(const struct gsm_e1_subslot *ss, u_int32_t callref)
+{
+	struct map_entry *me, *me2;
+	struct upqueue_entry *ue, *ue2;
+
+	if (ss)
+		llist_for_each_entry_safe(me, me2, &ss_map, list) {
+			if (!memcmp(&me->src, ss, sizeof(*ss)) ||
+			    !memcmp(&me->dst, ss, sizeof(*ss))) {
+				llist_del(&me->list);
+				return 0;
+			}
+		}
+	llist_for_each_entry_safe(ue, ue2, &ss_upqueue, list) {
+		if (ue->callref == callref) {
+			llist_del(&ue->list);
+			return 0;
+		}
+		if (ss && !memcmp(&ue->src, ss, sizeof(*ss))) {
+			llist_del(&ue->list);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+/* look-up an enty in the TRAU mux map */
+static struct gsm_e1_subslot *
+lookup_trau_mux_map(const struct gsm_e1_subslot *src)
+{
+	struct map_entry *me;
+
+	llist_for_each_entry(me, &ss_map, list) {
+		if (!memcmp(&me->src, src, sizeof(*src)))
+			return &me->dst;
+		if (!memcmp(&me->dst, src, sizeof(*src)))
+			return &me->src;
+	}
+	return NULL;
+}
+
+/* look-up an enty in the TRAU upqueue */
+struct upqueue_entry *
+lookup_trau_upqueue(const struct gsm_e1_subslot *src)
+{
+	struct upqueue_entry *ue;
+
+	llist_for_each_entry(ue, &ss_upqueue, list) {
+		if (!memcmp(&ue->src, src, sizeof(*src)))
+			return ue;
+	}
+	return NULL;
+}
+
+static const u_int8_t c_bits_check[] = { 0, 0, 0, 1, 0 };
+
+/* we get called by subchan_demux */
+int trau_mux_input(struct gsm_e1_subslot *src_e1_ss,
+		   const u_int8_t *trau_bits, int num_bits)
+{
+	struct decoded_trau_frame tf;
+	u_int8_t trau_bits_out[TRAU_FRAME_BITS];
+	struct gsm_e1_subslot *dst_e1_ss = lookup_trau_mux_map(src_e1_ss);
+	struct subch_mux *mx;
+	struct upqueue_entry *ue;
+	int rc;
+
+	/* decode TRAU, change it to downlink, re-encode */
+	rc = decode_trau_frame(&tf, trau_bits);
+	if (rc)
+		return rc;
+
+	if (!dst_e1_ss) {
+		struct msgb *msg;
+		struct gsm_data_frame *frame;
+		unsigned char *data;
+		int i, j, k, l, o;
+		/* frame shall be sent to upqueue */
+		if (!(ue = lookup_trau_upqueue(src_e1_ss)))
+			return -EINVAL;
+		if (!ue->callref)
+			return -EINVAL;
+		if (memcmp(tf.c_bits, c_bits_check, sizeof(c_bits_check)))
+			DEBUGPC(DMUX, "illegal trau (C1-C5) %s\n",
+				hexdump(tf.c_bits, sizeof(c_bits_check)));
+		msg = msgb_alloc(sizeof(struct gsm_data_frame) + 33,
+				 "GSM-DATA");
+		if (!msg)
+			return -ENOMEM;
+
+		frame = (struct gsm_data_frame *)msg->data;
+		memset(frame, 0, sizeof(struct gsm_data_frame));
+		data = frame->data;
+		data[0] = 0xd << 4;
+		/* reassemble d-bits */
+		i = 0; /* counts bits */
+		j = 4; /* counts output bits */
+		k = gsm_fr_map[0]-1; /* current number bit in element */
+		l = 0; /* counts element bits */
+		o = 0; /* offset input bits */
+		while (i < 260) {
+			data[j/8] |= (tf.d_bits[k+o] << (7-(j%8)));
+			if (--k < 0) {
+				o += gsm_fr_map[l];
+				k = gsm_fr_map[++l]-1;
+			}
+			i++;
+			j++;
+		}
+		frame->msg_type = GSM_TCHF_FRAME;
+		frame->callref = ue->callref;
+		trau_tx_to_mncc(ue->net, msg);
+
+		return 0;
+	}
+
+	mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts);
+	if (!mx)
+		return -EINVAL;
+
+	trau_frame_up2down(&tf);
+	encode_trau_frame(trau_bits_out, &tf);
+
+	/* and send it to the muxer */
+	return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out,
+				   TRAU_FRAME_BITS);
+}
+
+/* add receiver instance for lchan and callref */
+int trau_recv_lchan(struct gsm_lchan *lchan, u_int32_t callref)
+{
+	struct gsm_e1_subslot *src_ss;
+	struct upqueue_entry *ue;
+
+	ue = talloc(tall_upq_ctx, struct upqueue_entry);
+	if (!ue)
+		return -ENOMEM;
+
+	src_ss = &lchan->ts->e1_link;
+
+	DEBUGP(DCC, "Setting up TRAU receiver (e1=%u,ts=%u,ss=%u) "
+		"and (callref 0x%x)\n",
+		src_ss->e1_nr, src_ss->e1_ts, src_ss->e1_ts_ss,
+		callref);
+
+	/* make sure to get rid of any stale old mappings */
+	trau_mux_unmap(src_ss, callref);
+
+	memcpy(&ue->src, src_ss, sizeof(ue->src));
+	ue->net = lchan->ts->trx->bts->network;
+	ue->callref = callref;
+	llist_add(&ue->list, &ss_upqueue);
+
+	return 0;
+}
+
+int trau_send_frame(struct gsm_lchan *lchan, struct gsm_data_frame *frame)
+{
+	u_int8_t trau_bits_out[TRAU_FRAME_BITS];
+	struct gsm_e1_subslot *dst_e1_ss = &lchan->ts->e1_link;
+	struct subch_mux *mx;
+	int i, j, k, l, o;
+	unsigned char *data = frame->data;
+	struct decoded_trau_frame tf;
+
+	mx = e1inp_get_mux(dst_e1_ss->e1_nr, dst_e1_ss->e1_ts);
+	if (!mx)
+		return -EINVAL;
+
+	switch (frame->msg_type) {
+	case GSM_TCHF_FRAME:
+		/* set c-bits and t-bits */
+		tf.c_bits[0] = 1;
+		tf.c_bits[1] = 1;
+		tf.c_bits[2] = 1;
+		tf.c_bits[3] = 0;
+		tf.c_bits[4] = 0;
+		memset(&tf.c_bits[5], 0, 6);
+		memset(&tf.c_bits[11], 1, 10);
+		memset(&tf.t_bits[0], 1, 4);
+		/* reassemble d-bits */
+		i = 0; /* counts bits */
+		j = 4; /* counts input bits */
+		k = gsm_fr_map[0]-1; /* current number bit in element */
+		l = 0; /* counts element bits */
+		o = 0; /* offset output bits */
+		while (i < 260) {
+			tf.d_bits[k+o] = (data[j/8] >> (7-(j%8))) & 1;
+			if (--k < 0) {
+				o += gsm_fr_map[l];
+				k = gsm_fr_map[++l]-1;
+			}
+			i++;
+			j++;
+		}
+		break;
+	default:
+		DEBUGPC(DMUX, "unsupported message type %d\n",
+			frame->msg_type);
+		return -EINVAL;
+	}
+
+	encode_trau_frame(trau_bits_out, &tf);
+
+	/* and send it to the muxer */
+	return subchan_mux_enqueue(mx, dst_e1_ss->e1_ts_ss, trau_bits_out,
+				   TRAU_FRAME_BITS);
+}
diff --git a/src/libtrau/trau_upqueue.c b/src/libtrau/trau_upqueue.c
new file mode 100644
index 0000000..f8edaf0
--- /dev/null
+++ b/src/libtrau/trau_upqueue.c
@@ -0,0 +1,27 @@
+/* trau_upqueue.c - Pass msgb's up the chain */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+
+void trau_tx_to_mncc(struct gsm_network *net, struct msgb *msg)
+{
+	net->mncc_recv(net, msg);
+}
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
new file mode 100644
index 0000000..95b9ef4
--- /dev/null
+++ b/src/osmo-bsc/Makefile.am
@@ -0,0 +1,18 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = osmo-bsc
+
+
+osmo_bsc_SOURCES = osmo_bsc_main.c osmo_bsc_rf.c osmo_bsc_vty.c osmo_bsc_api.c \
+		   osmo_bsc_grace.c osmo_bsc_msc.c osmo_bsc_sccp.c \
+		   osmo_bsc_filter.c osmo_bsc_bssap.c osmo_bsc_audio.c
+# once again since TRAU uses CC symbol :(
+osmo_bsc_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+		 $(top_builddir)/src/libmsc/libmsc.a \
+		 $(top_builddir)/src/libbsc/libbsc.a \
+		 $(top_builddir)/src/libabis/libabis.a \
+		 $(top_builddir)/src/libtrau/libtrau.a \
+		 $(top_builddir)/src/libcommon/libcommon.a \
+		 $(LIBOSMOSCCP_LIBS)
diff --git a/src/osmo-bsc/Makefile.in b/src/osmo-bsc/Makefile.in
new file mode 100644
index 0000000..d83952c
--- /dev/null
+++ b/src/osmo-bsc/Makefile.in
@@ -0,0 +1,513 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = osmo-bsc$(EXEEXT)
+subdir = src/osmo-bsc
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_osmo_bsc_OBJECTS = osmo_bsc_main.$(OBJEXT) osmo_bsc_rf.$(OBJEXT) \
+	osmo_bsc_vty.$(OBJEXT) osmo_bsc_api.$(OBJEXT) \
+	osmo_bsc_grace.$(OBJEXT) osmo_bsc_msc.$(OBJEXT) \
+	osmo_bsc_sccp.$(OBJEXT) osmo_bsc_filter.$(OBJEXT) \
+	osmo_bsc_bssap.$(OBJEXT) osmo_bsc_audio.$(OBJEXT)
+osmo_bsc_OBJECTS = $(am_osmo_bsc_OBJECTS)
+am__DEPENDENCIES_1 =
+osmo_bsc_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libabis/libabis.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(osmo_bsc_SOURCES)
+DIST_SOURCES = $(osmo_bsc_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+osmo_bsc_SOURCES = osmo_bsc_main.c osmo_bsc_rf.c osmo_bsc_vty.c osmo_bsc_api.c \
+		   osmo_bsc_grace.c osmo_bsc_msc.c osmo_bsc_sccp.c \
+		   osmo_bsc_filter.c osmo_bsc_bssap.c osmo_bsc_audio.c
+
+# once again since TRAU uses CC symbol :(
+osmo_bsc_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+		 $(top_builddir)/src/libmsc/libmsc.a \
+		 $(top_builddir)/src/libbsc/libbsc.a \
+		 $(top_builddir)/src/libabis/libabis.a \
+		 $(top_builddir)/src/libtrau/libtrau.a \
+		 $(top_builddir)/src/libcommon/libcommon.a \
+		 $(LIBOSMOSCCP_LIBS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-bsc/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/osmo-bsc/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+osmo-bsc$(EXEEXT): $(osmo_bsc_OBJECTS) $(osmo_bsc_DEPENDENCIES) 
+	@rm -f osmo-bsc$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(osmo_bsc_OBJECTS) $(osmo_bsc_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_api.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_audio.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_bssap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_filter.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_grace.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_msc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_rf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_sccp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osmo_bsc_vty.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c
new file mode 100644
index 0000000..b8cbcf2
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_api.c
@@ -0,0 +1,174 @@
+/* (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/debug.h>
+
+#include <osmocore/protocol/gsm_08_08.h>
+#include <osmocore/gsm0808.h>
+
+#define return_when_not_connected(conn) \
+	if (!conn->sccp_con) {\
+		LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
+		return; \
+	}
+
+#define return_when_not_connected_val(conn, ret) \
+	if (!conn->sccp_con) {\
+		LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
+		return ret; \
+	}
+
+#define queue_msg_or_return(resp) \
+	if (!resp) { \
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \
+		return; \
+	} \
+	bsc_queue_for_msc(conn->sccp_con, resp);
+
+static uint16_t get_network_code_for_msc(struct gsm_network *net)
+{
+	if (net->msc_data->core_ncc != -1)
+		return net->msc_data->core_ncc;
+	return net->network_code;
+}
+
+static uint16_t get_country_code_for_msc(struct gsm_network *net)
+{
+	if (net->msc_data->core_mcc != -1)
+		return net->msc_data->core_mcc;
+	return net->country_code;
+}
+
+static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	resp = gsm0808_create_sapi_reject(dlci);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn,
+				  struct msgb *msg, uint8_t chosen_encr)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n");
+	resp = gsm0808_create_cipher_complete(msg, chosen_encr);
+	queue_msg_or_return(resp);
+}
+
+/*
+ * Instruct to reserve data for a new connectiom, create the complete
+ * layer three message, send it to open the connection.
+ */
+static int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+			uint16_t chosen_channel)
+{
+	struct msgb *resp;
+	uint16_t network_code = get_network_code_for_msc(conn->bts->network);
+	uint16_t country_code = get_country_code_for_msc(conn->bts->network);
+
+	/* allocate resource for a new connection */
+	if (bsc_create_new_connection(conn) != 0)
+		return BSC_API_CONN_POL_REJECT;
+
+	bsc_scan_bts_msg(conn, msg);
+	resp = gsm0808_create_layer3(msg, network_code, country_code,
+				     conn->bts->location_area_code,
+				     conn->bts->cell_identity);
+	if (!resp) {
+		LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n");
+		bsc_delete_connection(conn->sccp_con);
+		return BSC_API_CONN_POL_REJECT;
+	}
+
+	if (bsc_open_connection(conn->sccp_con, resp) != 0) {
+		bsc_delete_connection(conn->sccp_con);
+		msgb_free(resp);
+		return BSC_API_CONN_POL_REJECT;
+	}
+
+	return BSC_API_CONN_POL_ACCEPT;
+}
+
+static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	bsc_scan_bts_msg(conn, msg);
+	resp = gsm0808_create_dtap(msg, link_id);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause,
+			     uint8_t chosen_channel, uint8_t encr_alg_id,
+			     uint8_t speech_model)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel,
+						   encr_alg_id, speech_model);
+	queue_msg_or_return(resp);
+}
+
+static void bsc_assign_fail(struct gsm_subscriber_connection *conn,
+			    uint8_t cause, uint8_t *rr_cause)
+{
+	struct msgb *resp;
+	return_when_not_connected(conn);
+
+	resp = gsm0808_create_assignment_failure(cause, rr_cause);
+	queue_msg_or_return(resp);
+}
+
+static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	struct msgb *resp;
+	return_when_not_connected_val(conn, 1);
+
+	resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n");
+		return 0;
+	}
+
+	bsc_queue_for_msc(conn->sccp_con, resp);
+	return 0;
+}
+
+static struct bsc_api bsc_handler = {
+	.sapi_n_reject = bsc_sapi_n_reject,
+	.cipher_mode_compl = bsc_cipher_mode_compl,
+	.compl_l3 = bsc_compl_l3,
+	.dtap  = bsc_dtap,
+	.assign_compl = bsc_assign_compl,
+	.assign_fail = bsc_assign_fail,
+	.clear_request = bsc_clear_request,
+};
+
+struct bsc_api *osmo_bsc_api()
+{
+	return &bsc_handler;
+}
diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c
new file mode 100644
index 0000000..515cfa7
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_audio.c
@@ -0,0 +1,70 @@
+/*
+ * ipaccess audio handling
+ *
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+
+#include <arpa/inet.h>
+
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+				 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber_connection *con;
+	struct gsm_lchan *lchan = signal_data;
+	int rc;
+
+	if (subsys != SS_ABISIP)
+		return 0;
+
+	con = lchan->conn;
+	if (!con || !con->sccp_con)
+		return 0;
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		/* we can ask it to connect now */
+		LOGP(DMSC, LOGL_DEBUG, "Connecting BTS to port: %d conn: %d\n",
+		     con->sccp_con->rtp_port, lchan->abis_ip.conn_id);
+
+		rc = rsl_ipacc_mdcx(lchan, ntohl(INADDR_ANY),
+				    con->sccp_con->rtp_port,
+				    lchan->abis_ip.rtp_payload2);
+		if (rc < 0) {
+			LOGP(DMSC, LOGL_ERROR, "Failed to send MDCX: %d\n", rc);
+			return rc;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+int osmo_bsc_audio_init(struct gsm_network *net)
+{
+	net->hardcoded_rtp_payload = 98;
+	register_signal_handler(SS_ABISIP, handle_abisip_signal, net);
+	return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
new file mode 100644
index 0000000..f871131
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -0,0 +1,548 @@
+/* GSM 08.08 BSSMAP handling						*/
+/* (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/paging.h>
+
+#include <osmocore/gsm0808.h>
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <arpa/inet.h>
+
+static uint16_t read_data16(const uint8_t *data)
+{
+	uint16_t res;
+
+	memcpy(&res, data, sizeof(res));
+	return res;
+}
+
+/*
+ * helpers for the assignment command
+ */
+enum gsm0808_permitted_speech audio_support_to_gsm88(struct gsm_audio_support *audio)
+{
+	if (audio->hr) {
+		switch (audio->ver) {
+		case 1:
+			return GSM0808_PERM_HR1;
+			break;
+		case 2:
+			return GSM0808_PERM_HR2;
+			break;
+		case 3:
+			return GSM0808_PERM_HR3;
+			break;
+		default:
+			    LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver);
+			    return GSM0808_PERM_FR1;
+		}
+	} else {
+		switch (audio->ver) {
+		case 1:
+			return GSM0808_PERM_FR1;
+			break;
+		case 2:
+			return GSM0808_PERM_FR2;
+			break;
+		case 3:
+			return GSM0808_PERM_FR3;
+			break;
+		default:
+			LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver);
+			return GSM0808_PERM_HR1;
+		}
+	}
+}
+
+enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech)
+{
+	switch (speech) {
+	case GSM0808_PERM_HR1:
+	case GSM0808_PERM_FR1:
+		return GSM48_CMODE_SPEECH_V1;
+		break;
+	case GSM0808_PERM_HR2:
+	case GSM0808_PERM_FR2:
+		return GSM48_CMODE_SPEECH_EFR;
+		break;
+	case GSM0808_PERM_HR3:
+	case GSM0808_PERM_FR3:
+		return GSM48_CMODE_SPEECH_AMR;
+		break;
+	}
+
+	LOGP(DMSC, LOGL_FATAL, "Should not be reached.\n");
+	return GSM48_CMODE_SPEECH_AMR;
+}
+
+static int bssmap_handle_reset_ack(struct gsm_network *net,
+				   struct msgb *msg, unsigned int length)
+{
+	LOGP(DMSC, LOGL_NOTICE, "Reset ACK from MSC\n");
+	return 0;
+}
+
+/* GSM 08.08 § 3.2.1.19 */
+static int bssmap_handle_paging(struct gsm_network *net,
+				struct msgb *msg, unsigned int payload_length)
+{
+	struct gsm_subscriber *subscr;
+	struct tlv_parsed tp;
+	char mi_string[GSM48_MI_SIZE];
+	uint32_t tmsi = GSM_RESERVED_TMSI;
+	unsigned int lac = GSM_LAC_RESERVED_ALL_BTS;
+	uint8_t data_length;
+	const uint8_t *data;
+	uint8_t chan_needed = RSL_CHANNEED_ANY;
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandantory IMSI not present.\n");
+		return -1;
+	} else if ((TLVP_VAL(&tp, GSM0808_IE_IMSI)[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI) {
+		LOGP(DMSC, LOGL_ERROR, "Wrong content in the IMSI\n");
+		return -1;
+	}
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandantory CELL IDENTIFIER LIST not present.\n");
+		return -1;
+	}
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI)) {
+		gsm48_mi_to_string(mi_string, sizeof(mi_string),
+			   TLVP_VAL(&tp, GSM0808_IE_TMSI), TLVP_LEN(&tp, GSM0808_IE_TMSI));
+		tmsi = strtoul(mi_string, NULL, 10);
+	}
+
+
+	/*
+	 * parse the IMSI
+	 */
+	gsm48_mi_to_string(mi_string, sizeof(mi_string),
+			   TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI));
+
+	/*
+	 * parse the cell identifier list
+	 */
+	data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+	data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+
+	/*
+	 * Support paging to all network or one BTS at one LAC
+	 */
+	if (data_length == 3 && data[0] == CELL_IDENT_LAC) {
+		lac = ntohs(read_data16(&data[1]));
+	} else if (data_length > 1 || (data[0] & 0x0f) != CELL_IDENT_BSS) {
+		LOGP(DMSC, LOGL_ERROR, "Unsupported Cell Identifier List: %s\n", hexdump(data, data_length));
+		return -1;
+	}
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1)
+		chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03;
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) {
+		LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n");
+	}
+
+	subscr = subscr_get_or_create(net, mi_string);
+	if (!subscr) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate a subscriber for %s\n", mi_string);
+		return -1;
+	}
+
+	subscr->lac = lac;
+	subscr->tmsi = tmsi;
+
+	LOGP(DMSC, LOGL_DEBUG, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac);
+	paging_request(net, subscr, chan_needed, NULL, NULL);
+	return 0;
+}
+
+/*
+ * GSM 08.08 § 3.1.9.1 and 3.2.1.21...
+ * release our gsm_subscriber_connection and send message
+ */
+static int bssmap_handle_clear_command(struct osmo_bsc_sccp_con *conn,
+				       struct msgb *msg, unsigned int payload_length)
+{
+	struct msgb *resp;
+
+	/* TODO: handle the cause of this package */
+
+	if (conn->conn) {
+		LOGP(DMSC, LOGL_DEBUG, "Releasing all transactions on %p\n", conn);
+		gsm0808_clear(conn->conn);
+		subscr_con_free(conn->conn);
+		conn->conn = NULL;
+	}
+
+	/* send the clear complete message */
+	resp = gsm0808_create_clear_complete();
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n");
+		return -1;
+	}
+
+	bsc_queue_for_msc(conn, resp);
+	return 0;
+}
+
+/*
+ * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick
+ * the cipher to be used for this. In case we are already using
+ * a cipher we will have to send cipher mode reject to the MSC,
+ * otherwise we will have to pick something that we and the MS
+ * is supporting. Currently we are doing it in a rather static
+ * way by picking one ecnryption or no encrytpion.
+ */
+static int bssmap_handle_cipher_mode(struct osmo_bsc_sccp_con *conn,
+				     struct msgb *msg, unsigned int payload_length)
+{
+	uint16_t len;
+	struct gsm_network *network = NULL;
+	const uint8_t *data;
+	struct tlv_parsed tp;
+	struct msgb *resp;
+	int reject_cause = -1;
+	int include_imeisv = 1;
+
+	if (!conn->conn) {
+		LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+		goto reject;
+	}
+
+	if (conn->ciphering_handled) {
+		LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n");
+		goto reject;
+	}
+
+	conn->ciphering_handled = 1;
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) {
+		LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n");
+		goto reject;
+	}
+
+	/*
+	 * check if our global setting is allowed
+	 *  - Currently we check for A5/0 and A5/1
+	 *  - Copy the key if that is necessary
+	 *  - Otherwise reject
+	 */
+	len = TLVP_LEN(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+	if (len < 1) {
+		LOGP(DMSC, LOGL_ERROR, "IE Encryption Information is too short.\n");
+		goto reject;
+	}
+
+	network = conn->conn->bts->network;
+	data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+
+	if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE))
+		include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1;
+
+	if (network->a5_encryption == 0 && (data[0] & 0x1) == 0x1) {
+		gsm0808_cipher_mode(conn->conn, 0, NULL, 0, include_imeisv);
+	} else if (network->a5_encryption != 0 && (data[0] & 0x2) == 0x2) {
+		gsm0808_cipher_mode(conn->conn, 1, &data[1], len - 1, include_imeisv);
+	} else {
+		LOGP(DMSC, LOGL_ERROR, "Can not select encryption...\n");
+		goto reject;
+	}
+
+reject:
+	resp = gsm0808_create_cipher_reject(reject_cause);
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n");
+		return -1;
+	}
+
+	bsc_queue_for_msc(conn, resp);
+	return -1;
+}
+
+/*
+ * Handle the assignment request message.
+ *
+ * See §3.2.1.1 for the message type
+ */
+static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
+				     struct msgb *msg, unsigned int length)
+{
+	struct msgb *resp;
+	struct gsm_network *network;
+	struct tlv_parsed tp;
+	uint8_t *data;
+	uint16_t cic;
+	uint8_t timeslot;
+	uint8_t multiplex;
+	enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN;
+	int i, supported, port, full_rate = -1;
+
+	if (!conn->conn) {
+		LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+		return -1;
+	}
+
+	network = conn->conn->bts->network;
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) {
+		LOGP(DMSC, LOGL_ERROR, "Mandantory channel type not present.\n");
+		goto reject;
+	}
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+		LOGP(DMSC, LOGL_ERROR, "Identity code missing. Audio routing will not work.\n");
+		goto reject;
+	}
+
+	cic = ntohs(read_data16(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)));
+	timeslot = cic & 0x1f;
+	multiplex = (cic & ~0x1f) >> 5;
+
+	/*
+	 * Currently we only support a limited subset of all
+	 * possible channel types. The limitation ends by not using
+	 * multi-slot, limiting the channel coding, speech...
+	 */
+	if (TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE) < 3) {
+		LOGP(DMSC, LOGL_ERROR, "ChannelType len !=3 not supported: %d\n",
+			TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE));
+		goto reject;
+	}
+
+	/*
+	 * Try to figure out if we support the proposed speech codecs. For
+	 * now we will always pick the full rate codecs.
+	 */
+
+	data = (uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE);
+	if ((data[0] & 0xf) != 0x1) {
+		LOGP(DMSC, LOGL_ERROR, "ChannelType != speech: %d\n", data[0]);
+		goto reject;
+	}
+
+	if (data[1] != GSM0808_SPEECH_FULL_PREF && data[1] != GSM0808_SPEECH_HALF_PREF) {
+		LOGP(DMSC, LOGL_ERROR, "ChannelType full not allowed: %d\n", data[1]);
+		goto reject;
+	}
+
+	/*
+	 * go through the list of preferred codecs of our gsm network
+	 * and try to find it among the permitted codecs. If we found
+	 * it we will send chan_mode to the right mode and break the
+	 * inner loop. The outer loop will exit due chan_mode having
+	 * the correct value.
+	 */
+	full_rate = 0;
+	for (supported = 0;
+		chan_mode == GSM48_CMODE_SIGN && supported < network->msc_data->audio_length;
+		++supported) {
+
+		int perm_val = audio_support_to_gsm88(network->msc_data->audio_support[supported]);
+		for (i = 2; i < TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE); ++i) {
+			if ((data[i] & 0x7f) == perm_val) {
+				chan_mode = gsm88_to_chan_mode(perm_val);
+				full_rate = (data[i] & 0x4) == 0;
+				break;
+			} else if ((data[i] & 0x80) == 0x00) {
+				break;
+			}
+		}
+	}
+
+	if (chan_mode == GSM48_CMODE_SIGN) {
+		LOGP(DMSC, LOGL_ERROR, "No supported audio type found.\n");
+		goto reject;
+	}
+
+	/* map it to a MGCP Endpoint and a RTP port */
+	port = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+	conn->rtp_port = rtp_calculate_port(port,
+						network->msc_data->rtp_base);
+
+	return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
+
+reject:
+	resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+	if (!resp) {
+		LOGP(DMSC, LOGL_ERROR, "Channel allocation failure.\n");
+		return -1;
+	}
+
+	bsc_queue_for_msc(conn, resp);
+	return -1;
+}
+
+static int bssmap_rcvmsg_udt(struct gsm_network *net,
+			     struct msgb *msg, unsigned int length)
+{
+	int ret = 0;
+
+	if (length < 1) {
+		LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+		return -1;
+	}
+
+	switch (msg->l4h[0]) {
+	case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+		ret = bssmap_handle_reset_ack(net, msg, length);
+		break;
+	case BSS_MAP_MSG_PAGING:
+		if (bsc_grace_allow_new_connection(net))
+			ret = bssmap_handle_paging(net, msg, length);
+		break;
+	}
+
+	return ret;
+}
+
+static int bssmap_rcvmsg_dt1(struct osmo_bsc_sccp_con *conn,
+			     struct msgb *msg, unsigned int length)
+{
+	int ret = 0;
+
+	if (length < 1) {
+		LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+		return -1;
+	}
+
+	switch (msg->l4h[0]) {
+	case BSS_MAP_MSG_CLEAR_CMD:
+		ret = bssmap_handle_clear_command(conn, msg, length);
+		break;
+	case BSS_MAP_MSG_CIPHER_MODE_CMD:
+		ret = bssmap_handle_cipher_mode(conn, msg, length);
+		break;
+	case BSS_MAP_MSG_ASSIGMENT_RQST:
+		ret = bssmap_handle_assignm_req(conn, msg, length);
+		break;
+	default:
+		LOGP(DMSC, LOGL_DEBUG, "Unimplemented msg type: %d\n", msg->l4h[0]);
+		break;
+	}
+
+	return ret;
+}
+
+static int dtap_rcvmsg(struct osmo_bsc_sccp_con *conn,
+		       struct msgb *msg, unsigned int length)
+{
+	struct dtap_header *header;
+	struct msgb *gsm48;
+	uint8_t *data;
+
+	if (!conn->conn) {
+		LOGP(DMSC, LOGL_ERROR, "No subscriber connection available\n");
+		return -1;
+	}
+
+	header = (struct dtap_header *) msg->l3h;
+	if (sizeof(*header) >= length) {
+		LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %u got: %u\n", sizeof(*header), length);
+                LOGP(DMSC, LOGL_ERROR, "hex: %s\n", hexdump(msg->l3h, length));
+                return -1;
+	}
+
+	if (header->length > length - sizeof(*header)) {
+		LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length);
+                LOGP(DMSC, LOGL_ERROR, "hex: %s\n", hexdump(msg->l3h, length));
+		return -1;
+	}
+
+	LOGP(DMSC, LOGL_DEBUG, "DTAP message: SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0);
+
+	/* forward the data */
+	gsm48 = gsm48_msgb_alloc();
+	if (!gsm48) {
+		LOGP(DMSC, LOGL_ERROR, "Allocation of the message failed.\n");
+		return -1;
+	}
+
+	gsm48->l3h = gsm48->data;
+	data = msgb_put(gsm48, length - sizeof(*header));
+	memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header));
+
+	/* pass it to the filter for extra actions */
+	bsc_scan_msc_msg(conn->conn, gsm48);
+	return gsm0808_submit_dtap(conn->conn, gsm48, header->link_id, 1);
+}
+
+int bsc_handle_udt(struct gsm_network *network,
+		   struct bsc_msc_connection *conn,
+		   struct msgb *msgb, unsigned int length)
+{
+	struct bssmap_header *bs;
+
+	LOGP(DMSC, LOGL_DEBUG, "Incoming SCCP message ftom MSC: %s\n",
+		hexdump(msgb->l3h, length));
+
+	if (length < sizeof(*bs)) {
+		LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+		return -1;
+	}
+
+	bs = (struct bssmap_header *) msgb->l3h;
+	if (bs->length < length - sizeof(*bs))
+		return -1;
+
+	switch (bs->type) {
+	case BSSAP_MSG_BSS_MANAGEMENT:
+		msgb->l4h = &msgb->l3h[sizeof(*bs)];
+		bssmap_rcvmsg_udt(network, msgb, length - sizeof(*bs));
+		break;
+	default:
+		LOGP(DMSC, LOGL_ERROR, "Unimplemented msg type: %d\n", bs->type);
+	}
+
+	return 0;
+}
+
+int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn,
+		   struct msgb *msg, unsigned int len)
+{
+	if (len < sizeof(struct bssmap_header)) {
+		LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+	}
+
+	switch (msg->l3h[0]) {
+	case BSSAP_MSG_BSS_MANAGEMENT:
+		msg->l4h = &msg->l3h[sizeof(struct bssmap_header)];
+		bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header));
+		break;
+	case BSSAP_MSG_DTAP:
+		dtap_rcvmsg(conn, msg, len);
+		break;
+	default:
+		LOGP(DMSC, LOGL_DEBUG, "Unimplemented msg type: %d\n", msg->l3h[0]);
+	}
+
+	return -1;
+}
diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c
new file mode 100644
index 0000000..d2735a6
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_filter.c
@@ -0,0 +1,170 @@
+/* (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+
+#include <stdlib.h>
+
+static void handle_lu_request(struct gsm_subscriber_connection *conn,
+			      struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	struct gsm48_loc_upd_req *lu;
+	struct gsm48_loc_area_id lai;
+	struct gsm_network *net;
+
+	if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) {
+		LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg));
+		return;
+	}
+
+	net = conn->bts->network;
+
+	gh = msgb_l3(msg);
+	lu = (struct gsm48_loc_upd_req *) gh->data;
+
+	gsm48_generate_lai(&lai, net->country_code, net->network_code,
+			   conn->bts->location_area_code);
+
+	if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) {
+		LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n");
+		conn->sccp_con->new_subscriber = 1;
+	}
+}
+
+/* we will need to stop the paging request */
+static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	uint8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm48_hdr *gh;
+	struct gsm48_pag_resp *resp;
+	struct gsm_subscriber *subscr;
+
+	if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) {
+		LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg));
+		return -1;
+	}
+
+	gh = msgb_l3(msg);
+	resp = (struct gsm48_pag_resp *) &gh->data[0];
+
+	gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+				mi_string, &mi_type);
+	DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_active_by_tmsi(conn->bts->network,
+					       tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_active_by_imsi(conn->bts->network, mi_string);
+		break;
+	default:
+		subscr = NULL;
+		break;
+	}
+
+	if (!subscr) {
+		LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n");
+		return -1;
+	}
+
+	paging_request_stop(conn->bts, subscr, conn, msg);
+	subscr_put(subscr);
+	return 0;
+}
+
+/**
+ * This is used to scan a message for extra functionality of the BSC. This
+ * includes scanning for location updating requests/acceptd and then send
+ * a welcome USSD message to the subscriber.
+ */
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t pdisc = gh->proto_discr & 0x0f;
+	uint8_t mtype = gh->msg_type & 0xbf;
+
+	if (pdisc == GSM48_PDISC_MM) {
+		if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST)
+			handle_lu_request(conn, msg);
+	} else if (pdisc == GSM48_PDISC_RR) {
+		if (mtype == GSM48_MT_RR_PAG_RESP)
+			handle_page_resp(conn, msg);
+	}
+
+	return 0;
+}
+
+static void send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_network *net;
+	net = conn->bts->network;
+
+	if (!net->msc_data->ussd_welcome_txt)
+		return;
+
+	gsm0480_send_ussdNotify(conn, 1, net->msc_data->ussd_welcome_txt);
+	gsm0480_send_releaseComplete(conn);
+}
+
+/**
+ * Messages coming back from the MSC.
+ */
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm_network *net;
+	struct gsm48_loc_area_id *lai;
+	struct gsm48_hdr *gh;
+	uint8_t mtype;
+
+	if (msgb_l3len(msg) < sizeof(*gh)) {
+		LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n");
+		return -1;
+	}
+
+	gh = (struct gsm48_hdr *) msgb_l3(msg);
+	mtype = gh->msg_type & 0xbf;
+	net = conn->bts->network;
+
+	if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) {
+		if (net->msc_data->core_ncc != -1 ||
+		    net->msc_data->core_mcc != -1) {
+			if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) {
+				lai = (struct gsm48_loc_area_id *) &gh->data[0];
+				gsm48_generate_lai(lai, net->country_code,
+						   net->network_code,
+						   conn->bts->location_area_code);
+			}
+		}
+
+		if (conn->sccp_con->new_subscriber)
+			send_welcome_ussd(conn);
+	}
+
+	return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_grace.c b/src/osmo-bsc/osmo_bsc_grace.c
new file mode 100644
index 0000000..f699cf3
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_grace.c
@@ -0,0 +1,107 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/signal.h>
+
+int bsc_grace_allow_new_connection(struct gsm_network *network)
+{
+	if (!network->msc_data->rf_ctl)
+		return 1;
+	return network->msc_data->rf_ctl->policy == S_RF_ON;
+}
+
+static int handle_sub(struct gsm_lchan *lchan, const char *text)
+{
+	struct gsm_subscriber_connection *conn;
+
+	/* only send it to TCH */
+	if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F)
+		return -1;
+
+	/* only send on the primary channel */
+	conn = lchan->conn;
+	if (!conn)
+		return -1;
+
+	if (conn->lchan != lchan)
+		return -1;
+
+	/* only when active */
+	if (lchan->state != LCHAN_S_ACTIVE)
+		return -1;
+
+	gsm0480_send_ussdNotify(conn, 0, text);
+	gsm0480_send_releaseComplete(conn);
+
+	return 0;
+}
+
+/*
+ * The place to handle the grace mode. Right now we will send
+ * USSD messages to the subscriber, in the future we might start
+ * a timer to have different modes for the grace period.
+ */
+static int handle_grace(struct gsm_network *network)
+{
+	int ts_nr, lchan_nr;
+	struct gsm_bts *bts;
+	struct gsm_bts_trx *trx;
+
+	if (!network->msc_data->mid_call_txt)
+		return 0;
+
+	llist_for_each_entry(bts, &network->bts_list, list) {
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) {
+				struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+				for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) {
+					handle_sub(&ts->lchan[lchan_nr],
+						   network->msc_data->mid_call_txt);
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+static int handle_rf_signal(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct rf_signal_data *sig;
+
+	if (subsys != SS_RF)
+		return -1;
+
+	sig = signal_data;
+
+	if (signal == S_RF_GRACE)
+		handle_grace(sig->net);
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_grace(void)
+{
+	register_signal_handler(SS_RF, handle_rf_signal, NULL);
+}
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
new file mode 100644
index 0000000..b5f64ab
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -0,0 +1,261 @@
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/vty.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/process.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#include "../../bscconfig.h"
+
+static struct log_target *stderr_target;
+struct gsm_network *bsc_gsmnet = 0;
+static const char *config_file = "openbsc.cfg";
+static const char *rf_ctl = NULL;
+extern const char *openbsc_copyright;
+static int daemonize = 0;
+
+extern int bsc_bootstrap_network(int (*layer4)(struct gsm_network *, struct msgb *), const char *cfg_file);
+
+static void print_usage()
+{
+	printf("Usage: bsc_msc_ip\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -D --daemonize Fork the process into a background daemon\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+	printf("  -s --disable-color\n");
+	printf("  -T --timestamp. Print a timestamp in the debug output.\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -l --local=IP. The local address of the MGCP.\n");
+	printf("  -e --log-level number. Set a global loglevel.\n");
+	printf("  -r --rf-ctl NAME. A unix domain socket to listen for cmds.\n");
+	printf("  -t --testmode. A special mode to provoke failures at the MSC.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"daemonize", 0, 0, 'D'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"local", 1, 0, 'l'},
+			{"log-level", 1, 0, 'e'},
+			{"rf-ctl", 1, 0, 'r'},
+			{"testmode", 0, 0, 't'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:DsTc:e:r:t",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'c':
+			config_file = strdup(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'P':
+			ipacc_rtp_direct = 0;
+			break;
+		case 'e':
+			log_set_log_level(stderr_target, atoi(optarg));
+			break;
+		case 'r':
+			rf_ctl = optarg;
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+extern int bts_model_unknown_init(void);
+extern int bts_model_bs11_init(void);
+extern int bts_model_nanobts_init(void);
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OsmoBSC",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+extern int bsc_shutdown_net(struct gsm_network *net);
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		bsc_shutdown_net(bsc_gsmnet);
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+		sleep(3);
+		exit(0);
+		break;
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		if (!bsc_gsmnet->msc_data)
+			return;
+		if (!bsc_gsmnet->msc_data->msc_con)
+			return;
+		if (!bsc_gsmnet->msc_data->msc_con->is_connected)
+			return;
+		bsc_msc_lost(bsc_gsmnet->msc_data->msc_con);
+		break;
+	default:
+		break;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	log_init(&log_info);
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+
+	bts_model_unknown_init();
+	bts_model_bs11_init();
+	bts_model_nanobts_init();
+
+	/* enable filters */
+	log_set_all_filter(stderr_target, 1);
+
+	/* This needs to precede handle_options() */
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	bsc_vty_init();
+
+	/* parse options */
+	handle_options(argc, argv);
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	/* initialize SCCP */
+	sccp_set_log_area(DSCCP);
+
+
+	rc = bsc_bootstrap_network(NULL, config_file);
+	if (rc < 0) {
+		fprintf(stderr, "Bootstrapping the network failed. exiting.\n");
+		exit(1);
+	}
+	bsc_api_init(bsc_gsmnet, osmo_bsc_api());
+
+	if (rf_ctl) {
+		struct osmo_msc_data *data = bsc_gsmnet->msc_data;
+		data->rf_ctl = osmo_bsc_rf_create(rf_ctl, bsc_gsmnet);
+		if (!data->rf_ctl) {
+			fprintf(stderr, "Failed to create the RF service.\n");
+			exit(1);
+		}
+	}
+
+	if (osmo_bsc_msc_init(bsc_gsmnet) != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n");
+		exit(1);
+	}
+
+	if (osmo_bsc_sccp_init(bsc_gsmnet) != 0) {
+		LOGP(DNM, LOGL_ERROR, "Failed to register SCCP.\n");
+		exit(1);
+	}
+
+	if (osmo_bsc_audio_init(bsc_gsmnet) != 0) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to register audio support.\n");
+		exit(1);
+	}
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	signal(SIGPIPE, SIG_IGN);
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	while (1) {
+		bsc_select_main(0);
+	}
+
+	return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
new file mode 100644
index 0000000..2e8cf05
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -0,0 +1,368 @@
+/*
+ * Handle the connection to the MSC. This include ping/timeout/reconnect
+ * (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/signal.h>
+
+#include <osmocore/gsm0808.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+
+
+static void initialize_if_needed(struct bsc_msc_connection *conn);
+static void send_id_get_response(struct osmo_msc_data *data, int fd);
+static void send_ping(struct osmo_msc_data *data);
+
+/*
+ * MGCP forwarding code
+ */
+static int mgcp_do_read(struct bsc_fd *fd)
+{
+	struct osmo_msc_data *data = (struct osmo_msc_data *) fd->data;
+	struct msgb *mgcp;
+	int ret;
+
+	mgcp = msgb_alloc_headroom(4096, 128, "mgcp_from_gw");
+	if (!mgcp) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n");
+		return -1;
+	}
+
+	ret = read(fd->fd, mgcp->data, 4096 - 128);
+	if (ret <= 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno));
+		msgb_free(mgcp);
+		return -1;
+	} else if (ret > 4096 - 128) {
+		LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret);
+		msgb_free(mgcp);
+		return -1;
+        }
+
+	mgcp->l2h = msgb_put(mgcp, ret);
+	msc_queue_write(data->msc_con, mgcp, IPAC_PROTO_MGCP_OLD);
+	return 0;
+}
+
+static int mgcp_do_write(struct bsc_fd *fd, struct msgb *msg)
+{
+	int ret;
+
+	LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len);
+
+	ret = write(fd->fd, msg->data, msg->len);
+	if (ret != msg->len)
+		LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP GW (%s).\n", strerror(errno));
+
+	return ret;
+}
+
+static void mgcp_forward(struct osmo_msc_data *data, struct msgb *msg)
+{
+	struct msgb *mgcp;
+
+	if (msgb_l2len(msg) > 4096) {
+		LOGP(DMGCP, LOGL_ERROR, "Can not forward too big message.\n");
+		return;
+	}
+
+	mgcp = msgb_alloc(4096, "mgcp_to_gw");
+	if (!mgcp) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to send message.\n");
+		return;
+	}
+
+	msgb_put(mgcp, msgb_l2len(msg));
+	memcpy(mgcp->data, msg->l2h, mgcp->len);
+	if (write_queue_enqueue(&data->mgcp_agent, mgcp) != 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW.\n");
+		msgb_free(mgcp);
+	}
+}
+
+static int mgcp_create_port(struct osmo_msc_data *data)
+{
+	int on;
+	struct sockaddr_in addr;
+
+	data->mgcp_agent.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (data->mgcp_agent.bfd.fd < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno);
+		return -1;
+	}
+
+	on = 1;
+	setsockopt(data->mgcp_agent.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	/* try to bind the socket */
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+	addr.sin_port = 0;
+
+	if (bind(data->mgcp_agent.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to bind to any port.\n");
+		close(data->mgcp_agent.bfd.fd);
+		data->mgcp_agent.bfd.fd = -1;
+		return -1;
+	}
+
+	/* connect to the remote */
+	addr.sin_port = htons(2427);
+	if (connect(data->mgcp_agent.bfd.fd, (struct sockaddr *) & addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to connect to local MGCP GW. %s\n", strerror(errno));
+		close(data->mgcp_agent.bfd.fd);
+		data->mgcp_agent.bfd.fd = -1;
+		return -1;
+	}
+
+	write_queue_init(&data->mgcp_agent, 10);
+	data->mgcp_agent.bfd.when = BSC_FD_READ;
+	data->mgcp_agent.bfd.data = data;
+	data->mgcp_agent.read_cb = mgcp_do_read;
+	data->mgcp_agent.write_cb = mgcp_do_write;
+
+	if (bsc_register_fd(&data->mgcp_agent.bfd) != 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n");
+		close(data->mgcp_agent.bfd.fd);
+		data->mgcp_agent.bfd.fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Send data to the network
+ */
+int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto)
+{
+	ipaccess_prepend_header(msg, proto);
+	if (write_queue_enqueue(&conn->write_queue, msg) != 0) {
+		LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto);
+		msgb_free(msg);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int msc_alink_do_write(struct bsc_fd *fd, struct msgb *msg)
+{
+	int ret;
+
+	LOGP(DMSC, LOGL_DEBUG, "Sending SCCP to MSC: %u\n", msgb_l2len(msg));
+	LOGP(DMI, LOGL_DEBUG, "MSC TX %s\n", hexdump(msg->data, msg->len));
+
+	ret = write(fd->fd, msg->data, msg->len);
+	if (ret < msg->len)
+		perror("MSC: Failed to send SCCP");
+
+	return ret;
+}
+
+static int ipaccess_a_fd_cb(struct bsc_fd *bfd)
+{
+	int error;
+	struct msgb *msg = ipaccess_read_msg(bfd, &error);
+	struct ipaccess_head *hh;
+	struct osmo_msc_data *data = (struct osmo_msc_data *) bfd->data;
+
+	if (!msg) {
+		if (error == 0) {
+			LOGP(DMSC, LOGL_ERROR, "The connection to the MSC was lost.\n");
+			bsc_msc_lost(data->msc_con);
+			return -1;
+		}
+
+		LOGP(DMSC, LOGL_ERROR, "Failed to parse ip access message: %d\n", error);
+		return -1;
+	}
+
+	LOGP(DMSC, LOGL_DEBUG, "From MSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]);
+
+	/* handle base message handling */
+	hh = (struct ipaccess_head *) msg->data;
+	ipaccess_rcvmsg_base(msg, bfd);
+
+	/* initialize the networking. This includes sending a GSM08.08 message */
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_ID_ACK)
+			initialize_if_needed(data->msc_con);
+		else if (msg->l2h[0] == IPAC_MSGT_ID_GET) {
+			send_id_get_response(data, bfd->fd);
+		} else if (msg->l2h[0] == IPAC_MSGT_PONG) {
+			bsc_del_timer(&data->pong_timer);
+		}
+	} else if (hh->proto == IPAC_PROTO_SCCP) {
+		sccp_system_incoming(msg);
+	} else if (hh->proto == IPAC_PROTO_MGCP_OLD) {
+		mgcp_forward(data, msg);
+	}
+
+	msgb_free(msg);
+	return 0;
+}
+
+static void send_ping(struct osmo_msc_data *data)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "ping");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create PING.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = IPAC_MSGT_PING;
+
+	msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS);
+}
+
+static void msc_ping_timeout_cb(void *_data)
+{
+	struct osmo_msc_data *data = (struct osmo_msc_data *) _data;
+	if (data->ping_timeout < 0)
+		return;
+
+	send_ping(data);
+
+	/* send another ping in 20 seconds */
+	bsc_schedule_timer(&data->ping_timer, data->ping_timeout, 0);
+
+	/* also start a pong timer */
+	bsc_schedule_timer(&data->pong_timer, data->pong_timeout, 0);
+}
+
+static void msc_pong_timeout_cb(void *_data)
+{
+	struct osmo_msc_data *data = (struct osmo_msc_data *) _data;
+
+	LOGP(DMSC, LOGL_ERROR, "MSC didn't answer PING. Closing connection.\n");
+	bsc_msc_lost(data->msc_con);
+}
+
+static void msc_connection_connected(struct bsc_msc_connection *con)
+{
+	struct msc_signal_data sig;
+	struct osmo_msc_data *data;
+	int ret, on;
+	on = 1;
+	ret = setsockopt(con->write_queue.bfd.fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+	if (ret != 0)
+                LOGP(DMSC, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
+
+	data = (struct osmo_msc_data *) con->write_queue.bfd.data;
+	msc_ping_timeout_cb(data);
+
+	sig.data = data;
+	dispatch_signal(SS_MSC, S_MSC_CONNECTED, &sig);
+}
+
+/*
+ * The connection to the MSC was lost and we will need to free all
+ * resources and then attempt to reconnect.
+ */
+static void msc_connection_was_lost(struct bsc_msc_connection *msc)
+{
+	struct msc_signal_data sig;
+	struct osmo_msc_data *data;
+
+	LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n");
+
+	data = (struct osmo_msc_data *) msc->write_queue.bfd.data;
+	bsc_del_timer(&data->ping_timer);
+	bsc_del_timer(&data->pong_timer);
+
+	sig.data = data;
+	dispatch_signal(SS_MSC, S_MSC_LOST, &sig);
+
+	msc->is_authenticated = 0;
+	bsc_msc_schedule_connect(msc);
+}
+
+static void initialize_if_needed(struct bsc_msc_connection *conn)
+{
+	struct msgb *msg;
+
+	if (!conn->is_authenticated) {
+		/* send a gsm 08.08 reset message from here */
+		msg = gsm0808_create_reset();
+		if (!msg) {
+			LOGP(DMSC, LOGL_ERROR, "Failed to create the reset message.\n");
+			return;
+		}
+
+		sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0);
+		msgb_free(msg);
+		conn->is_authenticated = 1;
+	}
+}
+
+static void send_id_get_response(struct osmo_msc_data *data, int fd)
+{
+	struct msgb *msg;
+
+	msg = bsc_msc_id_get_resp(data->bsc_token);
+	if (!msg)
+		return;
+	msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS);
+}
+
+int osmo_bsc_msc_init(struct gsm_network *network)
+{
+	struct osmo_msc_data *data = network->msc_data;
+
+	if (mgcp_create_port(data) != 0)
+		return -1;
+
+	data->msc_con = bsc_msc_create(data->msc_ip,
+				       data->msc_port,
+				       data->msc_ip_dscp);
+	if (!data->msc_con) {
+		LOGP(DMSC, LOGL_ERROR, "Creating the MSC network connection failed.\n");
+		return -1;
+	}
+
+	data->ping_timer.cb = msc_ping_timeout_cb;
+	data->ping_timer.data = data;
+	data->pong_timer.cb = msc_pong_timeout_cb;
+	data->pong_timer.data = data;
+
+	data->msc_con->write_queue.bfd.data = data;
+	data->msc_con->connection_loss = msc_connection_was_lost;
+	data->msc_con->connected = msc_connection_connected;
+	data->msc_con->write_queue.read_cb = ipaccess_a_fd_cb;
+	data->msc_con->write_queue.write_cb = msc_alink_do_write;
+	bsc_msc_connect(data->msc_con);
+
+	return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_rf.c b/src/osmo-bsc/osmo_bsc_rf.c
new file mode 100644
index 0000000..5652c9d
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_rf.c
@@ -0,0 +1,364 @@
+/* RF Ctl handling socket */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/osmo_msc_data.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/protocol/gsm_12_21.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#define RF_CMD_QUERY '?'
+#define RF_CMD_OFF   '0'
+#define RF_CMD_ON    '1'
+#define RF_CMD_D_OFF 'd'
+#define RF_CMD_ON_G  'g'
+
+static int lock_each_trx(struct gsm_network *net, int lock)
+{
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		struct gsm_bts_trx *trx;
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			gsm_trx_lock_rf(trx, lock);
+		}
+	}
+
+	return 0;
+}
+
+static void send_resp(struct osmo_bsc_rf_conn *conn, char send)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc(10, "RF Query");
+	if (!msg) {
+		LOGP(DINP, LOGL_ERROR, "Failed to allocate response msg.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = send;
+
+	if (write_queue_enqueue(&conn->queue, msg) != 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to enqueue the answer.\n");
+		msgb_free(msg);
+		return;
+	}
+
+	return;
+}
+
+
+/*
+ * Send a
+ *    'g' when we are in grace mode
+ *    '1' when one TRX is online,
+ *    '0' otherwise
+ */
+static void handle_query(struct osmo_bsc_rf_conn *conn)
+{
+	struct gsm_bts *bts;
+	char send = RF_CMD_OFF;
+
+	if (conn->rf->policy == S_RF_GRACE)
+		return send_resp(conn, RF_CMD_ON_G);
+
+	llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) {
+		struct gsm_bts_trx *trx;
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			if (trx->nm_state.availability == NM_AVSTATE_OK &&
+			    trx->nm_state.operational != NM_STATE_LOCKED) {
+					send = RF_CMD_ON;
+					break;
+			}
+		}
+	}
+
+	send_resp(conn, send);
+}
+
+static void rf_check_cb(void *_data)
+{
+	struct gsm_bts *bts;
+	struct osmo_bsc_rf *rf = _data;
+
+	llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) {
+		struct gsm_bts_trx *trx;
+
+		/* don't bother to check a booting or missing BTS */
+		if (!bts->oml_link || !is_ipaccess_bts(bts))
+			continue;
+
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			if (trx->nm_state.availability != NM_AVSTATE_OK ||
+			    trx->nm_state.operational != NM_OPSTATE_ENABLED ||
+			    trx->nm_state.administrative != NM_STATE_UNLOCKED) {
+				LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n");
+				ipaccess_drop_oml(bts);
+				break;
+			}
+		}
+	}
+}
+
+static void send_signal(struct osmo_bsc_rf *rf, int val)
+{
+	struct rf_signal_data sig;
+	sig.net = rf->gsm_network;
+
+	rf->policy = val;
+	dispatch_signal(SS_RF, val, &sig);
+}
+
+static int switch_rf_off(struct osmo_bsc_rf *rf)
+{
+	lock_each_trx(rf->gsm_network, 1);
+	send_signal(rf, S_RF_OFF);
+
+	return 0;
+}
+
+static void grace_timeout(void *_data)
+{
+	struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data;
+
+	LOGP(DINP, LOGL_NOTICE, "Grace timeout. Disabling the TRX.\n");
+	switch_rf_off(rf);
+}
+
+static int enter_grace(struct osmo_bsc_rf *rf)
+{
+	rf->grace_timeout.cb = grace_timeout;
+	rf->grace_timeout.data = rf;
+	bsc_schedule_timer(&rf->grace_timeout, rf->gsm_network->msc_data->mid_call_timeout, 0);
+	LOGP(DINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n",
+	     rf->gsm_network->msc_data->mid_call_timeout);
+
+	send_signal(rf, S_RF_GRACE);
+	return 0;
+}
+
+static void rf_delay_cmd_cb(void *data)
+{
+	struct osmo_bsc_rf *rf = data;
+
+	switch (rf->last_request) {
+	case RF_CMD_D_OFF:
+		rf->last_state_command = "RF Direct Off";
+		bsc_del_timer(&rf->rf_check);
+		bsc_del_timer(&rf->grace_timeout);
+		switch_rf_off(rf);
+		break;
+	case RF_CMD_ON:
+		rf->last_state_command = "RF Direct On";
+		bsc_del_timer(&rf->grace_timeout);
+		lock_each_trx(rf->gsm_network, 0);
+		send_signal(rf, S_RF_ON);
+		bsc_schedule_timer(&rf->rf_check, 3, 0);
+		break;
+	case RF_CMD_OFF:
+		rf->last_state_command = "RF Scheduled Off";
+		bsc_del_timer(&rf->rf_check);
+		enter_grace(rf);
+		break;
+	}
+}
+
+static int rf_read_cmd(struct bsc_fd *fd)
+{
+	struct osmo_bsc_rf_conn *conn = fd->data;
+	char buf[1];
+	int rc;
+
+	rc = read(fd->fd, buf, sizeof(buf));
+	if (rc != sizeof(buf)) {
+		LOGP(DINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno));
+		bsc_unregister_fd(fd);
+		close(fd->fd);
+		write_queue_clear(&conn->queue);
+		talloc_free(conn);
+		return -1;
+	}
+
+	switch (buf[0]) {
+	case RF_CMD_QUERY:
+		handle_query(conn);
+		break;
+	case RF_CMD_D_OFF:
+	case RF_CMD_ON:
+	case RF_CMD_OFF:
+		conn->rf->last_request = buf[0];
+		if (!bsc_timer_pending(&conn->rf->delay_cmd))
+			bsc_schedule_timer(&conn->rf->delay_cmd, 1, 0);
+		break;
+	default:
+		conn->rf->last_state_command = "Unknown command";
+		LOGP(DINP, LOGL_ERROR, "Unknown command %d\n", buf[0]);
+		break;
+	}
+
+	return 0;
+}
+
+static int rf_write_cmd(struct bsc_fd *fd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(fd->fd, msg->data, msg->len);
+	if (rc != msg->len) {
+		LOGP(DINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int rf_ctl_accept(struct bsc_fd *bfd, unsigned int what)
+{
+	struct osmo_bsc_rf_conn *conn;
+	struct osmo_bsc_rf *rf = bfd->data;
+	struct sockaddr_un addr;
+	socklen_t len = sizeof(addr);
+	int fd;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &addr, &len);
+	if (fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n",
+		     errno, strerror(errno));
+		return -1;
+	}
+
+	conn = talloc_zero(rf, struct osmo_bsc_rf_conn);
+	if (!conn) {
+		LOGP(DINP, LOGL_ERROR, "Failed to allocate mem.\n");
+		close(fd);
+		return -1;
+	}
+
+	write_queue_init(&conn->queue, 10);
+	conn->queue.bfd.data = conn;
+	conn->queue.bfd.fd = fd;
+	conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+	conn->queue.read_cb = rf_read_cmd;
+	conn->queue.write_cb = rf_write_cmd;
+	conn->rf = rf;
+
+	if (bsc_register_fd(&conn->queue.bfd) != 0) {
+		close(fd);
+		talloc_free(conn);
+		return -1;
+	}
+
+	return 0;
+}
+
+struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net)
+{
+	unsigned int namelen;
+	struct sockaddr_un local;
+	struct bsc_fd *bfd;
+	struct osmo_bsc_rf *rf;
+	int rc;
+
+	rf = talloc_zero(NULL, struct osmo_bsc_rf);
+	if (!rf) {
+		LOGP(DINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n");
+		return NULL;
+	}
+
+	bfd = &rf->listen;
+	bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (bfd->fd < 0) {
+		LOGP(DINP, LOGL_ERROR, "Can not create socket. %d/%s\n",
+		     errno, strerror(errno));
+		return NULL;
+	}
+
+	local.sun_family = AF_UNIX;
+	strncpy(local.sun_path, path, sizeof(local.sun_path));
+	local.sun_path[sizeof(local.sun_path) - 1] = '\0';
+	unlink(local.sun_path);
+
+	/* we use the same magic that X11 uses in Xtranssock.c for
+	 * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+	local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+	namelen = SUN_LEN(&local);
+#else
+	namelen = strlen(local.sun_path) +
+		  offsetof(struct sockaddr_un, sun_path);
+#endif
+
+	rc = bind(bfd->fd, (struct sockaddr *) &local, namelen);
+	if (rc != 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n",
+		     local.sun_path, errno, strerror(errno));
+		close(bfd->fd);
+		talloc_free(rf);
+		return NULL;
+	}
+
+	if (listen(bfd->fd, 0) != 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno));
+		close(bfd->fd);
+		talloc_free(rf);
+		return NULL;
+	}
+
+	bfd->when = BSC_FD_READ;
+	bfd->cb = rf_ctl_accept;
+	bfd->data = rf;
+
+	if (bsc_register_fd(bfd) != 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to register bfd.\n");
+		close(bfd->fd);
+		talloc_free(rf);
+		return NULL;
+	}
+
+	rf->gsm_network = net;
+	rf->policy = S_RF_ON;
+	rf->last_state_command = "";
+
+	/* check the rf state */
+	rf->rf_check.data = rf;
+	rf->rf_check.cb = rf_check_cb;
+
+	/* delay cmd handling */
+	rf->delay_cmd.data = rf;
+	rf->delay_cmd.cb = rf_delay_cmd_cb;
+
+	return rf;
+}
+
diff --git a/src/osmo-bsc/osmo_bsc_sccp.c b/src/osmo-bsc/osmo_bsc_sccp.c
new file mode 100644
index 0000000..1abb473
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_sccp.c
@@ -0,0 +1,288 @@
+/* Interaction with the SCCP subsystem */
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/signal.h>
+
+#include <osmocore/gsm0808.h>
+#include <osmocore/talloc.h>
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/* SCCP helper */
+#define SCCP_IT_TIMER 60
+
+static LLIST_HEAD(active_connections);
+
+static void free_queued(struct osmo_bsc_sccp_con *conn)
+{
+	struct msgb *msg;
+
+	while (!llist_empty(&conn->sccp_queue)) {
+		/* this is not allowed to fail */
+		msg = msgb_dequeue(&conn->sccp_queue);
+		msgb_free(msg);
+	}
+
+	conn->sccp_queue_size = 0;
+}
+
+static void send_queued(struct osmo_bsc_sccp_con *conn)
+{
+	struct msgb *msg;
+
+	while (!llist_empty(&conn->sccp_queue)) {
+		/* this is not allowed to fail */
+		msg = msgb_dequeue(&conn->sccp_queue);
+		sccp_connection_write(conn->sccp, msg);
+		msgb_free(msg);
+		conn->sccp_queue_size -= 1;
+	}
+}
+
+static void msc_outgoing_sccp_data(struct sccp_connection *conn,
+				   struct msgb *msg, unsigned int len)
+{
+	struct osmo_bsc_sccp_con *bsc_con =
+			(struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+	bsc_handle_dt1(bsc_con, msg, len);
+}
+
+static void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state)
+{
+	struct osmo_bsc_sccp_con *con_data;
+
+	if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+		con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+		if(con_data->conn) {
+			LOGP(DMSC, LOGL_ERROR,
+				"ERROR: The lchan is still associated\n.");
+			gsm0808_clear(con_data->conn);
+			subscr_con_free(con_data->conn);
+			con_data->conn = NULL;
+		}
+
+		con_data->sccp = NULL;
+		free_queued(con_data);
+		sccp_connection_free(conn);
+		bsc_delete_connection(con_data);
+	} else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn);
+		con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+		bsc_del_timer(&con_data->sccp_cc_timeout);
+		bsc_schedule_timer(&con_data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+
+		send_queued(con_data);
+	}
+}
+
+static void bsc_sccp_force_free(struct osmo_bsc_sccp_con *data)
+{
+	if (data->conn) {
+		gsm0808_clear(data->conn);
+		subscr_con_free(data->conn);
+		data->conn = NULL;
+	}
+
+	free_queued(data);
+	sccp_connection_force_free(data->sccp);
+	data->sccp = NULL;
+	bsc_delete_connection(data);
+}
+
+static void sccp_it_timeout(void *_data)
+{
+	struct osmo_bsc_sccp_con *data =
+		(struct osmo_bsc_sccp_con *) _data;
+
+	sccp_connection_send_it(data->sccp);
+	bsc_schedule_timer(&data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+}
+
+static void sccp_cc_timeout(void *_data)
+{
+	struct osmo_bsc_sccp_con *data =
+		(struct osmo_bsc_sccp_con *) _data;
+
+	if (data->sccp->connection_state >= SCCP_CONNECTION_STATE_ESTABLISHED)
+		return;
+
+	LOGP(DMSC, LOGL_ERROR, "The connection was never established.\n");
+	bsc_sccp_force_free(data);
+}
+
+static void msc_sccp_write_ipa(struct sccp_connection *conn, struct msgb *msg, void *data)
+{
+	struct gsm_network *net = (struct gsm_network *) data;
+	msc_queue_write(net->msc_data->msc_con, msg, IPAC_PROTO_SCCP);
+}
+
+static int msc_sccp_accept(struct sccp_connection *connection, void *data)
+{
+	LOGP(DMSC, LOGL_DEBUG, "Rejecting incoming SCCP connection.\n");
+	return -1;
+}
+
+static int msc_sccp_read(struct msgb *msgb, unsigned int length, void *data)
+{
+	struct gsm_network *net = (struct gsm_network *) data;
+	return bsc_handle_udt(net, net->msc_data->msc_con, msgb, length);
+}
+
+int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+	struct sccp_connection *sccp = conn->sccp;
+
+	if (sccp->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+		LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+		msgb_free(msg);
+	} else if (sccp->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED
+		   && conn->sccp_queue_size == 0) {
+		sccp_connection_write(sccp, msg);
+		msgb_free(msg);
+	} else if (conn->sccp_queue_size > 10) {
+		LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+		msgb_free(msg);
+	} else {
+		LOGP(DMSC, LOGL_DEBUG, "Queueing packet on %p. Queue size: %d\n", sccp, conn->sccp_queue_size);
+		conn->sccp_queue_size += 1;
+		msgb_enqueue(&conn->sccp_queue, msg);
+	}
+
+	return 0;
+}
+
+int bsc_create_new_connection(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_network *net;
+	struct osmo_bsc_sccp_con *bsc_con;
+	struct sccp_connection *sccp;
+
+	net = conn->bts->network;
+	if (!net->msc_data->msc_con->is_authenticated) {
+		LOGP(DMSC, LOGL_ERROR, "Not connected to a MSC. Not forwarding data.\n");
+		return -1;
+	}
+
+	if (!bsc_grace_allow_new_connection(net)) {
+		LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n");
+		return -1;
+	}
+
+	sccp = sccp_connection_socket();
+	if (!sccp) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n");
+		return -ENOMEM;
+	}
+
+	bsc_con = talloc_zero(conn->bts, struct osmo_bsc_sccp_con);
+	if (!bsc_con) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate.\n");
+		sccp_connection_free(sccp);
+		return -1;
+	}
+
+	/* callbacks */
+	sccp->state_cb = msc_outgoing_sccp_state;
+	sccp->data_cb = msc_outgoing_sccp_data;
+	sccp->data_ctx = bsc_con;
+
+	/* prepare the timers */
+	bsc_con->sccp_it_timeout.cb = sccp_it_timeout;
+	bsc_con->sccp_it_timeout.data = bsc_con;
+	bsc_con->sccp_cc_timeout.cb = sccp_cc_timeout;
+	bsc_con->sccp_cc_timeout.data = bsc_con;
+
+	INIT_LLIST_HEAD(&bsc_con->sccp_queue);
+
+	bsc_con->sccp = sccp;
+	bsc_con->msc_con = net->msc_data->msc_con;
+	bsc_con->conn = conn;
+	llist_add(&bsc_con->entry, &active_connections);
+	conn->sccp_con = bsc_con;
+	return 0;
+}
+
+int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+	bsc_schedule_timer(&conn->sccp_cc_timeout, 10, 0);
+	sccp_connection_connect(conn->sccp, &sccp_ssn_bssap, msg);
+	msgb_free(msg);
+	return 0;
+}
+
+int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp)
+{
+	if (!sccp)
+		return 0;
+
+	if (sccp->conn)
+		LOGP(DMSC, LOGL_ERROR, "Should have been cleared.\n");
+
+	llist_del(&sccp->entry);
+	bsc_del_timer(&sccp->sccp_it_timeout);
+	bsc_del_timer(&sccp->sccp_cc_timeout);
+	talloc_free(sccp);
+	return 0;
+}
+
+static void bsc_close_connections(struct bsc_msc_connection *msc_con)
+{
+	struct osmo_bsc_sccp_con *con, *tmp;
+
+	llist_for_each_entry_safe(con, tmp, &active_connections, entry)
+		bsc_sccp_force_free(con);
+}
+
+static int handle_msc_signal(unsigned int subsys, unsigned int signal,
+			     void *handler_data, void *signal_data)
+{
+	struct osmo_msc_data *data;
+
+	if (subsys != SS_MSC)
+		return 0;
+
+	data = (struct osmo_msc_data *) signal_data;
+	if (signal == S_MSC_LOST)
+		bsc_close_connections(data->msc_con);
+
+	return 0;
+}
+
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet)
+{
+	sccp_set_log_area(DSCCP);
+	sccp_system_init(msc_sccp_write_ipa, gsmnet);
+	sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL);
+	sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, gsmnet);
+
+	register_signal_handler(SS_MSC, handle_msc_signal, gsmnet);
+
+	return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c
new file mode 100644
index 0000000..1667742
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_vty.c
@@ -0,0 +1,311 @@
+/* Osmo BSC VTY Configuration */
+/* (C) 2009-2010 by Holger Hans Peter Freyther
+ * (C) 2009-2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/vty.h>
+
+#include <osmocore/talloc.h>
+
+
+#define IPA_STR "IP.ACCESS specific\n"
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct osmo_msc_data *osmo_msc_data(struct vty *vty)
+{
+	return bsc_gsmnet->msc_data;
+}
+
+static struct cmd_node msc_node = {
+	MSC_NODE,
+	"%s(msc)#",
+	1,
+};
+
+DEFUN(cfg_net_msc, cfg_net_msc_cmd,
+      "msc", "Configure MSC details")
+{
+	vty->index = bsc_gsmnet;
+	vty->node = MSC_NODE;
+
+	return CMD_SUCCESS;
+}
+
+static int config_write_msc(struct vty *vty)
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+
+	vty_out(vty, " msc%s", VTY_NEWLINE);
+	if (data->bsc_token)
+		vty_out(vty, "  token %s%s", data->bsc_token, VTY_NEWLINE);
+	if (data->core_ncc != -1)
+		vty_out(vty, "  core-mobile-network-code %d%s",
+			data->core_ncc, VTY_NEWLINE);
+	if (data->core_mcc != -1)
+		vty_out(vty, "  core-mobile-country-code %d%s",
+			data->core_mcc, VTY_NEWLINE);
+	vty_out(vty, "  ip.access rtp-base %d%s", data->rtp_base, VTY_NEWLINE);
+	vty_out(vty, "  ip %s%s", data->msc_ip, VTY_NEWLINE);
+	vty_out(vty, "  port %d%s", data->msc_port, VTY_NEWLINE);
+	vty_out(vty, "  ip-dscp %d%s", data->msc_ip_dscp, VTY_NEWLINE);
+	vty_out(vty, "  timeout-ping %d%s", data->ping_timeout, VTY_NEWLINE);
+	vty_out(vty, "  timeout-pong %d%s", data->pong_timeout, VTY_NEWLINE);
+	if (data->mid_call_txt)
+		vty_out(vty, "mid-call-text %s%s", data->mid_call_txt, VTY_NEWLINE);
+	vty_out(vty, " mid-call-timeout %d%s", data->mid_call_timeout, VTY_NEWLINE);
+	if (data->ussd_welcome_txt)
+		vty_out(vty, " bsc-welcome-text %s%s", data->ussd_welcome_txt, VTY_NEWLINE);
+
+	if (data->audio_length != 0) {
+		int i;
+
+		vty_out(vty, " codec_list ");
+		for (i = 0; i < data->audio_length; ++i) {
+			if (i != 0)
+				vty_out(vty, ", ");
+
+			if (data->audio_support[i]->hr)
+				vty_out(vty, "hr%.1u", data->audio_support[i]->ver);
+			else
+				vty_out(vty, "fr%.1u", data->audio_support[i]->ver);
+		}
+		vty_out(vty, "%s", VTY_NEWLINE);
+
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_token,
+      cfg_net_bsc_token_cmd,
+      "token TOKEN",
+      "A token for the BSC to be sent to the MSC")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+
+	bsc_replace_string(data, &data->bsc_token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ncc,
+      cfg_net_bsc_ncc_cmd,
+      "core-mobile-network-code <1-999>",
+      "Use this network code for the backbone\n" "NCC value\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->core_ncc = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mcc,
+      cfg_net_bsc_mcc_cmd,
+      "core-mobile-country-code <1-999>",
+      "Use this country code for the backbone\n" "MCC value\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->core_mcc = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_rtp_base,
+      cfg_net_bsc_rtp_base_cmd,
+      "ip.access rtp-base <1-65000>",
+      IPA_STR
+      "Set the rtp-base port for the RTP stream\n"
+      "Port number\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->rtp_base = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_codec_list,
+      cfg_net_bsc_codec_list_cmd,
+      "codec-list .LIST",
+      "Set the allowed audio codecs\n"
+      "List of audio codecs\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	int saw_fr, saw_hr;
+	int i;
+
+	saw_fr = saw_hr = 0;
+
+	/* free the old list... if it exists */
+	if (data->audio_support) {
+		talloc_free(data->audio_support);
+		data->audio_support = NULL;
+		data->audio_length = 0;
+	}
+
+	/* create a new array */
+	data->audio_support =
+		talloc_zero_array(data, struct gsm_audio_support *, argc);
+	data->audio_length = argc;
+
+	for (i = 0; i < argc; ++i) {
+		/* check for hrX or frX */
+		if (strlen(argv[i]) != 3
+				|| argv[i][1] != 'r'
+				|| (argv[i][0] != 'h' && argv[i][0] != 'f')
+				|| argv[i][2] < 0x30
+				|| argv[i][2] > 0x39)
+			goto error;
+
+		data->audio_support[i] = talloc_zero(data->audio_support,
+				struct gsm_audio_support);
+		data->audio_support[i]->ver = atoi(argv[i] + 2);
+
+		if (strncmp("hr", argv[i], 2) == 0) {
+			data->audio_support[i]->hr = 1;
+			saw_hr = 1;
+		} else if (strncmp("fr", argv[i], 2) == 0) {
+			data->audio_support[i]->hr = 0;
+			saw_fr = 1;
+		}
+
+		if (saw_hr && saw_fr) {
+			vty_out(vty, "Can not have full-rate and half-rate codec.%s",
+					VTY_NEWLINE);
+			return CMD_ERR_INCOMPLETE;
+		}
+	}
+
+	return CMD_SUCCESS;
+
+error:
+	vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s",
+			argv[i], VTY_NEWLINE);
+	return CMD_ERR_INCOMPLETE;
+}
+
+DEFUN(cfg_net_msc_ip,
+      cfg_net_msc_ip_cmd,
+      "ip A.B.C.D", "Set the MSC/MUX IP address.")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+
+	bsc_replace_string(data, &data->msc_ip, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_port,
+      cfg_net_msc_port_cmd,
+      "port <1-65000>",
+      "Set the MSC/MUX port.")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->msc_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_msc_prio,
+      cfg_net_msc_prio_cmd,
+      "ip-dscp <0-255>",
+      "Set the IP_TOS socket attribite")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->msc_ip_dscp = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_ping_time,
+      cfg_net_msc_ping_time_cmd,
+      "timeout-ping NR",
+      "Set the PING interval, negative for not sending PING")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->ping_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_pong_time,
+      cfg_net_msc_pong_time_cmd,
+      "timeout-pong NR",
+      "Set the time to wait for a PONG.")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->pong_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_mid_call_text,
+      cfg_net_msc_mid_call_text_cmd,
+      "mid-call-text .TEXT",
+      "Set the USSD notifcation to be send.\n" "Text to be sent\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	char *txt = argv_concat(argv, argc, 0);
+	if (!txt)
+		return CMD_WARNING;
+
+	bsc_replace_string(data, &data->mid_call_txt, txt);
+	talloc_free(txt);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_mid_call_timeout,
+      cfg_net_msc_mid_call_timeout_cmd,
+      "mid-call-timeout NR",
+      "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	data->mid_call_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_welcome_ussd,
+      cfg_net_msc_welcome_ussd_cmd,
+      "bsc-welcome-text .TEXT",
+      "Set the USSD notification to be sent.\n" "Text to be sent\n")
+{
+	struct osmo_msc_data *data = osmo_msc_data(vty);
+	char *str = argv_concat(argv, argc, 0);
+	if (!str)
+		return CMD_WARNING;
+
+	bsc_replace_string(data, &data->ussd_welcome_txt, str);
+	talloc_free(str);
+	return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+	install_element(GSMNET_NODE, &cfg_net_msc_cmd);
+	install_node(&msc_node, config_write_msc);
+	install_default(MSC_NODE);
+	install_element(MSC_NODE, &cfg_net_bsc_token_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd);
+	install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_ip_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_port_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_prio_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_mid_call_text_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_mid_call_timeout_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
+
+	return 0;
+}
diff --git a/src/osmo-bsc_mgcp/Makefile.am b/src/osmo-bsc_mgcp/Makefile.am
new file mode 100644
index 0000000..32cc813
--- /dev/null
+++ b/src/osmo-bsc_mgcp/Makefile.am
@@ -0,0 +1,10 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = bsc_mgcp
+
+bsc_mgcp_SOURCES = mgcp_main.c
+bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		 $(top_builddir)/src/libmgcp/libmgcp.a \
+		 $(LIBOSMOVTY_LIBS)
diff --git a/src/osmo-bsc_mgcp/Makefile.in b/src/osmo-bsc_mgcp/Makefile.in
new file mode 100644
index 0000000..714fae3
--- /dev/null
+++ b/src/osmo-bsc_mgcp/Makefile.in
@@ -0,0 +1,487 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = bsc_mgcp$(EXEEXT)
+subdir = src/osmo-bsc_mgcp
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_bsc_mgcp_OBJECTS = mgcp_main.$(OBJEXT)
+bsc_mgcp_OBJECTS = $(am_bsc_mgcp_OBJECTS)
+am__DEPENDENCIES_1 =
+bsc_mgcp_DEPENDENCIES = $(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a $(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(bsc_mgcp_SOURCES)
+DIST_SOURCES = $(bsc_mgcp_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+bsc_mgcp_SOURCES = mgcp_main.c
+bsc_mgcp_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		 $(top_builddir)/src/libmgcp/libmgcp.a \
+		 $(LIBOSMOVTY_LIBS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-bsc_mgcp/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/osmo-bsc_mgcp/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+bsc_mgcp$(EXEEXT): $(bsc_mgcp_OBJECTS) $(bsc_mgcp_DEPENDENCIES) 
+	@rm -f bsc_mgcp$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(bsc_mgcp_OBJECTS) $(bsc_mgcp_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_main.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/osmo-bsc_mgcp/mgcp_main.c b/src/osmo-bsc_mgcp/mgcp_main.c
new file mode 100644
index 0000000..c8d9a62
--- /dev/null
+++ b/src/osmo-bsc_mgcp/mgcp_main.c
@@ -0,0 +1,283 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* The main method to drive it as a standalone process      */
+
+/*
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <osmocore/process.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/select.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/command.h>
+
+#include "../../bscconfig.h"
+
+/* this is here for the vty... it will never be called */
+void subscr_put() { abort(); }
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#warning "Make use of the rtp proxy code"
+
+static struct mgcp_config *cfg;
+static int reset_endpoints = 0;
+static int daemonize = 0;
+
+const char *openbsc_copyright =
+	"Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n"
+	"Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+	"Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static char *config_file = "mgcp.cfg";
+
+/* used by msgb and mgcp */
+void *tall_bsc_ctx = NULL;
+
+static void print_help()
+{
+	printf("Some useful help...\n");
+	printf(" -h --help is printing this text.\n");
+	printf(" -c --config-file filename The config file to use.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"config-file", 1, 0, 'c'},
+			{"daemonize", 0, 0, 'D'},
+			{"version", 0, 0, 'V'},
+			{0, 0, 0, 0},
+		};
+
+		c = getopt_long(argc, argv, "hc:VD", long_options, &option_index);
+
+		if (c == -1)
+			break;
+
+		switch(c) {
+		case 'h':
+			print_help();
+			exit(0);
+			break;
+		case 'c':
+			config_file = talloc_strdup(tall_bsc_ctx, optarg);
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		default:
+			/* ignore */
+			break;
+		};
+	}
+}
+
+/* simply remember this */
+static int mgcp_rsip_cb(struct mgcp_config *cfg)
+{
+	reset_endpoints = 1;
+
+	return 0;
+}
+
+static int mgcp_change_cb(struct mgcp_trunk_config *cfg, int endpoint, int state)
+{
+	if (state != MGCP_ENDP_MDCX)
+		return 0;
+
+	mgcp_send_dummy(&cfg->endpoints[endpoint]);
+	return 0;
+}
+
+static int read_call_agent(struct bsc_fd *fd, unsigned int what)
+{
+	struct sockaddr_in addr;
+	socklen_t slen = sizeof(addr);
+	struct msgb *msg;
+	struct msgb *resp;
+	int i;
+
+	msg = (struct msgb *) fd->data;
+
+	/* read one less so we can use it as a \0 */
+	int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0,
+		(struct sockaddr *) &addr, &slen);
+	if (rc < 0) {
+		perror("Gateway failed to read");
+		return -1;
+	} else if (slen > sizeof(addr)) {
+		fprintf(stderr, "Gateway received message from outerspace: %lu %d\n",
+			slen, sizeof(addr));
+		return -1;
+	}
+
+	/* handle message now */
+	msg->l2h = msgb_put(msg, rc);
+	resp = mgcp_handle_message(cfg, msg);
+	msgb_reset(msg);
+
+	if (resp) {
+		sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr));
+		msgb_free(resp);
+	}
+
+	if (reset_endpoints) {
+		LOGP(DMGCP, LOGL_NOTICE, "Asked to reset endpoints.\n");
+		reset_endpoints = 0;
+
+		/* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */
+		for (i = 1; i < cfg->trunk.number_endpoints; ++i)
+			mgcp_free_endp(&cfg->trunk.endpoints[i]);
+	}
+
+	return 0;
+}
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OpenBSC MGCP",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+int main(int argc, char **argv)
+{
+	struct gsm_network dummy_network;
+	struct sockaddr_in addr;
+	int on = 1, rc;
+	struct log_target *stderr_target;
+
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent");
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	cfg = mgcp_config_alloc();
+	if (!cfg)
+		return -1;
+
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	logging_vty_add_cmds();
+	mgcp_vty_init();
+
+	handle_options(argc, argv);
+
+        rc = mgcp_parse_config(config_file, cfg);
+	if (rc < 0)
+		return rc;
+
+	rc = telnet_init(tall_bsc_ctx, &dummy_network, 4243);
+	if (rc < 0)
+		return rc;
+
+	/* set some callbacks */
+	cfg->reset_cb = mgcp_rsip_cb;
+	cfg->change_cb = mgcp_change_cb;
+
+        /* we need to bind a socket */
+        if (rc == 0) {
+		cfg->gw_fd.bfd.when = BSC_FD_READ;
+		cfg->gw_fd.bfd.cb = read_call_agent;
+		cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+		if (cfg->gw_fd.bfd.fd < 0) {
+			perror("Gateway failed to listen");
+			return -1;
+		}
+
+		setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+		memset(&addr, 0, sizeof(addr));
+		addr.sin_family = AF_INET;
+		addr.sin_port = htons(cfg->source_port);
+		inet_aton(cfg->source_addr, &addr.sin_addr);
+
+		if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+			perror("Gateway failed to bind");
+			return -1;
+		}
+
+		cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg");
+		if (!cfg->gw_fd.bfd.data) {
+			fprintf(stderr, "Gateway memory error.\n");
+			return -1;
+		}
+
+
+		if (bsc_register_fd(&cfg->gw_fd.bfd) != 0) {
+			LOGP(DMGCP, LOGL_FATAL, "Failed to register the fd\n");
+			return -1;
+		}
+
+		LOGP(DMGCP, LOGL_NOTICE, "Configured for MGCP.\n");
+	}
+
+	/* initialisation */
+	srand(time(NULL));
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	/* main loop */
+	while (1) {
+		bsc_select_main(0);
+	}
+
+
+	return 0;
+}
diff --git a/src/osmo-bsc_nat/Makefile.am b/src/osmo-bsc_nat/Makefile.am
new file mode 100644
index 0000000..98a3431
--- /dev/null
+++ b/src/osmo-bsc_nat/Makefile.am
@@ -0,0 +1,15 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = osmo-bsc_nat
+
+
+osmo_bsc_nat_SOURCES = bsc_filter.c bsc_mgcp_utils.c bsc_nat.c bsc_nat_utils.c \
+		  bsc_nat_vty.c bsc_sccp.c bsc_ussd.c
+osmo_bsc_nat_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		$(top_builddir)/src/libmgcp/libmgcp.a \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libabis/libabis.a \
+		$(top_builddir)/src/libtrau/libtrau.a \
+		-lrt $(LIBOSMOSCCP_LIBS)
diff --git a/src/osmo-bsc_nat/Makefile.in b/src/osmo-bsc_nat/Makefile.in
new file mode 100644
index 0000000..ba69f7e
--- /dev/null
+++ b/src/osmo-bsc_nat/Makefile.in
@@ -0,0 +1,504 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = osmo-bsc_nat$(EXEEXT)
+subdir = src/osmo-bsc_nat
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_osmo_bsc_nat_OBJECTS = bsc_filter.$(OBJEXT) \
+	bsc_mgcp_utils.$(OBJEXT) bsc_nat.$(OBJEXT) \
+	bsc_nat_utils.$(OBJEXT) bsc_nat_vty.$(OBJEXT) \
+	bsc_sccp.$(OBJEXT) bsc_ussd.$(OBJEXT)
+osmo_bsc_nat_OBJECTS = $(am_osmo_bsc_nat_OBJECTS)
+am__DEPENDENCIES_1 =
+osmo_bsc_nat_DEPENDENCIES = $(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libabis/libabis.a \
+	$(top_builddir)/src/libtrau/libtrau.a $(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(osmo_bsc_nat_SOURCES)
+DIST_SOURCES = $(osmo_bsc_nat_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS)
+osmo_bsc_nat_SOURCES = bsc_filter.c bsc_mgcp_utils.c bsc_nat.c bsc_nat_utils.c \
+		  bsc_nat_vty.c bsc_sccp.c bsc_ussd.c
+
+osmo_bsc_nat_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		$(top_builddir)/src/libmgcp/libmgcp.a \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libabis/libabis.a \
+		$(top_builddir)/src/libtrau/libtrau.a \
+		-lrt $(LIBOSMOSCCP_LIBS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-bsc_nat/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/osmo-bsc_nat/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+osmo-bsc_nat$(EXEEXT): $(osmo_bsc_nat_OBJECTS) $(osmo_bsc_nat_DEPENDENCIES) 
+	@rm -f osmo-bsc_nat$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(osmo_bsc_nat_OBJECTS) $(osmo_bsc_nat_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_filter.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_mgcp_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_sccp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_ussd.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/osmo-bsc_nat/bsc_filter.c b/src/osmo-bsc_nat/bsc_filter.c
new file mode 100644
index 0000000..73e9878
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_filter.c
@@ -0,0 +1,216 @@
+/* BSC Multiplexer/NAT */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/debug.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/*
+ * The idea is to have a simple struct describing a IPA packet with
+ * SCCP SSN and the GSM 08.08 payload and decide. We will both have
+ * a white and a blacklist of packets we want to handle.
+ *
+ * TODO: Implement a "NOT" in the filter language.
+ */
+
+#define ALLOW_ANY -1
+
+#define FILTER_TO_BSC	1
+#define FILTER_TO_MSC	2
+#define FILTER_TO_BOTH	3
+
+
+struct bsc_pkt_filter {
+	int ipa_proto;
+	int dest_ssn;
+	int bssap;
+	int gsm;
+	int filter_dir;
+};
+
+static struct bsc_pkt_filter black_list[] = {
+	/* filter reset messages to the MSC */
+	{ IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET, FILTER_TO_MSC },
+
+	/* filter reset ack messages to the BSC */
+	{ IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET_ACKNOWLEDGE, FILTER_TO_BSC },
+
+	/* filter ip access */
+	{ IPAC_PROTO_IPACCESS, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_MSC },
+};
+
+static struct bsc_pkt_filter white_list[] = {
+	/* allow IPAC_PROTO_SCCP messages to both sides */
+	{ IPAC_PROTO_SCCP, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH },
+
+	/* allow MGCP messages to both sides */
+	{ IPAC_PROTO_MGCP_OLD, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH },
+};
+
+struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg)
+{
+	struct sccp_parse_result result;
+	struct bsc_nat_parsed *parsed;
+	struct ipaccess_head *hh;
+
+	/* quick fail */
+	if (msg->len < 4)
+		return NULL;
+
+	parsed = talloc_zero(msg, struct bsc_nat_parsed);
+	if (!parsed)
+		return NULL;
+
+	/* more init */
+	parsed->ipa_proto = parsed->called_ssn = parsed->calling_ssn = -1;
+	parsed->sccp_type = parsed->bssap = parsed->gsm_type = -1;
+
+	/* start parsing */
+	hh = (struct ipaccess_head *) msg->data;
+	parsed->ipa_proto = hh->proto;
+
+	msg->l2h = &hh->data[0];
+
+	/* do a size check on the input */
+	if (ntohs(hh->len) != msgb_l2len(msg)) {
+		LOGP(DINP, LOGL_ERROR, "Wrong input length?\n");
+		talloc_free(parsed);
+		return NULL;
+	}
+
+	/* analyze sccp down here */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+		memset(&result, 0, sizeof(result));
+		if (sccp_parse_header(msg, &result) != 0) {
+			talloc_free(parsed);
+			return 0;
+		}
+
+		if (msg->l3h && msgb_l3len(msg) < 3) {
+			LOGP(DNAT, LOGL_ERROR, "Not enough space or GSM payload\n");
+			talloc_free(parsed);
+			return 0;
+		}
+
+		parsed->sccp_type = sccp_determine_msg_type(msg);
+		parsed->src_local_ref = result.source_local_reference;
+		parsed->dest_local_ref = result.destination_local_reference;
+		parsed->called_ssn = result.called.ssn;
+		parsed->calling_ssn = result.calling.ssn;
+
+		/* in case of connection confirm we have no payload */
+		if (msg->l3h) {
+			parsed->bssap = msg->l3h[0];
+			parsed->gsm_type = msg->l3h[2];
+		}
+	}
+
+	return parsed;
+}
+
+int bsc_nat_filter_ipa(int dir, struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+	int i;
+
+	/* go through the blacklist now */
+	for (i = 0; i < ARRAY_SIZE(black_list); ++i) {
+		/* ignore the rule? */
+		if (black_list[i].filter_dir != FILTER_TO_BOTH
+		    && black_list[i].filter_dir != dir)
+			continue;
+
+		/* the proto is not blacklisted */
+		if (black_list[i].ipa_proto != ALLOW_ANY
+		    && black_list[i].ipa_proto != parsed->ipa_proto)
+			continue;
+
+		if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+			/* the SSN is not blacklisted */
+			if (black_list[i].dest_ssn != ALLOW_ANY
+			    && black_list[i].dest_ssn != parsed->called_ssn)
+				continue;
+
+			/* bssap */
+			if (black_list[i].bssap != ALLOW_ANY
+			    && black_list[i].bssap != parsed->bssap)
+				continue;
+
+			/* gsm */
+			if (black_list[i].gsm != ALLOW_ANY
+			    && black_list[i].gsm != parsed->gsm_type)
+				continue;
+
+			/* blacklisted */
+			LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i);
+			return 1;
+		} else {
+			/* blacklisted, we have no content sniffing yet */
+			LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i);
+			return 1;
+		}
+	}
+
+	/* go through the whitelust now */
+	for (i = 0; i < ARRAY_SIZE(white_list); ++i) {
+		/* ignore the rule? */
+		if (white_list[i].filter_dir != FILTER_TO_BOTH
+		    && white_list[i].filter_dir != dir)
+			continue;
+
+		/* the proto is not whitelisted */
+		if (white_list[i].ipa_proto != ALLOW_ANY
+		    && white_list[i].ipa_proto != parsed->ipa_proto)
+			continue;
+
+		if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+			/* the SSN is not whitelisted */
+			if (white_list[i].dest_ssn != ALLOW_ANY
+			    && white_list[i].dest_ssn != parsed->called_ssn)
+				continue;
+
+			/* bssap */
+			if (white_list[i].bssap != ALLOW_ANY
+			    && white_list[i].bssap != parsed->bssap)
+				continue;
+
+			/* gsm */
+			if (white_list[i].gsm != ALLOW_ANY
+			    && white_list[i].gsm != parsed->gsm_type)
+				continue;
+
+			/* whitelisted */
+			LOGP(DNAT, LOGL_INFO, "Whitelisted with rule %d\n", i);
+			return 0;
+		} else {
+			/* whitelisted */
+			return 0;
+		}
+	}
+
+	return 1;
+}
diff --git a/src/osmo-bsc_nat/bsc_mgcp_utils.c b/src/osmo-bsc_nat/bsc_mgcp_utils.c
new file mode 100644
index 0000000..9eac00b
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_mgcp_utils.c
@@ -0,0 +1,764 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/gsm0808.h>
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+int bsc_mgcp_nr_multiplexes(int max_endpoints)
+{
+	int div = max_endpoints / 32;
+
+	if ((max_endpoints % 32) != 0)
+		div += 1;
+
+	return div;
+}
+
+static int bsc_init_endps_if_needed(struct bsc_connection *con)
+{
+	int multiplexes;
+
+	/* we have done that */
+	if (con->_endpoint_status)
+		return 0;
+
+	/* we have no config... */
+	if (!con->cfg)
+		return -1;
+
+	multiplexes = bsc_mgcp_nr_multiplexes(con->cfg->max_endpoints);
+	con->number_multiplexes = multiplexes;
+	con->max_endpoints = con->cfg->max_endpoints;
+	con->_endpoint_status = talloc_zero_array(con, char, 32 * multiplexes + 1);
+	return con->_endpoint_status == NULL;
+}
+
+static int bsc_assign_endpoint(struct bsc_connection *bsc, struct sccp_connections *con)
+{
+	int multiplex;
+	int timeslot;
+	const int number_endpoints = bsc->max_endpoints;
+	int i;
+
+	mgcp_endpoint_to_timeslot(bsc->last_endpoint, &multiplex, &timeslot);
+	timeslot += 1;
+
+	for (i = 0; i < number_endpoints; ++i) {
+		int endpoint;
+
+		/* Wrap around timeslots */
+		if (timeslot == 0)
+			timeslot = 1;
+
+		if (timeslot == 0x1f) {
+			timeslot = 1;
+			multiplex += 1;
+		}
+
+		/* Wrap around the multiplex */
+		if (multiplex >= bsc->number_multiplexes)
+			multiplex = 0;
+
+		endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+		/* Now check if we are allowed to assign this one */
+		if (endpoint >= bsc->max_endpoints) {
+			multiplex = 0;
+			timeslot = 1;
+			endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+		}
+
+
+		if (bsc->_endpoint_status[endpoint] == 0) {
+			bsc->_endpoint_status[endpoint] = 1;
+			con->bsc_endp = endpoint;
+			bsc->last_endpoint = endpoint;
+			return 0;
+		}
+
+		timeslot += 1;
+	}
+
+	return -1;
+}
+
+static uint16_t create_cic(int endpoint)
+{
+	int timeslot, multiplex;
+
+	mgcp_endpoint_to_timeslot(endpoint, &multiplex, &timeslot);
+	return (multiplex << 5) | (timeslot & 0x1f);
+}
+
+int bsc_mgcp_assign_patch(struct sccp_connections *con, struct msgb *msg)
+{
+	struct sccp_connections *mcon;
+	struct tlv_parsed tp;
+	uint16_t cic;
+	uint8_t timeslot;
+	uint8_t multiplex;
+	unsigned int endp;
+
+	if (!msg->l3h) {
+		LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n");
+		return -1;
+	}
+
+	if (msgb_l3len(msg) < 3) {
+		LOGP(DNAT, LOGL_ERROR, "Assignment message has not enough space for GSM0808.\n");
+		return -1;
+	}
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+		LOGP(DNAT, LOGL_ERROR, "Circuit identity code not found in assignment message.\n");
+		return -1;
+	}
+
+	cic = ntohs(*(uint16_t *)TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+	timeslot = cic & 0x1f;
+	multiplex = (cic & ~0x1f) >> 5;
+
+
+	endp = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+	if (endp >= con->bsc->nat->mgcp_cfg->trunk.number_endpoints) {
+		LOGP(DNAT, LOGL_ERROR,
+			"MSC attempted to assign bad endpoint 0x%x\n",
+			endp);
+		return -1;
+	}
+
+	/* find stale connections using that endpoint */
+	llist_for_each_entry(mcon, &con->bsc->nat->sccp_connections, list_entry) {
+		if (mcon->msc_endp == endp) {
+			LOGP(DNAT, LOGL_ERROR,
+			     "Endpoint %d was assigned to 0x%x and now 0x%x\n",
+			     endp,
+			     sccp_src_ref_to_int(&mcon->patched_ref),
+			     sccp_src_ref_to_int(&con->patched_ref));
+			bsc_mgcp_dlcx(mcon);
+		}
+	}
+
+	con->msc_endp = endp;
+	if (bsc_init_endps_if_needed(con->bsc) != 0)
+		return -1;
+	if (bsc_assign_endpoint(con->bsc, con) != 0)
+		return -1;
+
+	/*
+	 * now patch the message for the new CIC...
+	 * still assumed to be one multiplex only
+	 */
+	cic = htons(create_cic(con->bsc_endp));
+	memcpy((uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE),
+		&cic, sizeof(cic));
+
+	return 0;
+}
+
+static void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i)
+{
+	if (nat->bsc_endpoints[i].transaction_id) {
+		talloc_free(nat->bsc_endpoints[i].transaction_id);
+		nat->bsc_endpoints[i].transaction_id = NULL;
+	}
+
+	nat->bsc_endpoints[i].transaction_state = 0;
+	nat->bsc_endpoints[i].bsc = NULL;
+}
+
+void bsc_mgcp_free_endpoints(struct bsc_nat *nat)
+{
+	int i;
+
+	for (i = 1; i < nat->mgcp_cfg->trunk.number_endpoints; ++i){
+		bsc_mgcp_free_endpoint(nat, i);
+		mgcp_free_endp(&nat->mgcp_cfg->trunk.endpoints[i]);
+	}
+}
+
+/* send a MDCX where we do not want a response */
+static void bsc_mgcp_send_mdcx(struct bsc_connection *bsc, int port, struct mgcp_endpoint *endp)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+		       "MDCX 23 %x@mgw MGCP 1.0\r\n"
+		       "Z: noanswer\r\n"
+		       "\r\n"
+		       "c=IN IP4 %s\r\n"
+		       "m=audio %d RTP/AVP 255\r\n",
+		       port,
+		       bsc->nat->mgcp_cfg->source_addr,
+		       endp->bts_end.local_port);
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+		return;
+	}
+
+	#warning "The MDCX is not send to the BSC. It should"
+}
+
+static void bsc_mgcp_send_dlcx(struct bsc_connection *bsc, int endpoint)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+		       "DLCX 26 %x@mgw MGCP 1.0\r\n"
+		       "Z: noanswer\r\n", endpoint);
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+		return;
+	}
+
+	bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+void bsc_mgcp_init(struct sccp_connections *con)
+{
+	con->msc_endp = -1;
+	con->bsc_endp = -1;
+}
+
+void bsc_mgcp_dlcx(struct sccp_connections *con)
+{
+	/* send a DLCX down the stream */
+	if (con->bsc_endp != -1 && con->bsc->_endpoint_status) {
+		if (con->bsc->_endpoint_status[con->bsc_endp] != 1)
+			LOGP(DNAT, LOGL_ERROR, "Endpoint 0x%x was not in use\n", con->bsc_endp);
+		con->bsc->_endpoint_status[con->bsc_endp] = 0;
+		bsc_mgcp_send_dlcx(con->bsc, con->bsc_endp);
+		bsc_mgcp_free_endpoint(con->bsc->nat, con->msc_endp);
+	}
+
+	bsc_mgcp_init(con);
+}
+
+
+struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint)
+{
+	struct sccp_connections *con = NULL;
+	struct sccp_connections *sccp;
+
+	llist_for_each_entry(sccp, &nat->sccp_connections, list_entry) {
+		if (sccp->msc_endp == -1)
+			continue;
+		if (sccp->msc_endp != endpoint)
+			continue;
+
+		con = sccp;
+	}
+
+	if (con)
+		return con;
+
+	LOGP(DMGCP, LOGL_ERROR, "Failed to find the connection.\n");
+	return NULL;
+}
+
+int bsc_mgcp_policy_cb(struct mgcp_trunk_config *tcfg, int endpoint, int state, const char *transaction_id)
+{
+	struct bsc_nat *nat;
+	struct bsc_endpoint *bsc_endp;
+	struct sccp_connections *sccp;
+	struct mgcp_endpoint *mgcp_endp;
+	struct msgb *bsc_msg;
+
+	nat = tcfg->cfg->data;
+	bsc_endp = &nat->bsc_endpoints[endpoint];
+	mgcp_endp = &nat->mgcp_cfg->trunk.endpoints[endpoint];
+
+	if (bsc_endp->transaction_id) {
+		LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x had pending transaction: '%s'\n",
+		     endpoint, bsc_endp->transaction_id);
+		talloc_free(bsc_endp->transaction_id);
+		bsc_endp->transaction_id = NULL;
+		bsc_endp->transaction_state = 0;
+	}
+	bsc_endp->bsc = NULL;
+
+	sccp = bsc_mgcp_find_con(nat, endpoint);
+
+	if (!sccp) {
+		LOGP(DMGCP, LOGL_ERROR, "Did not find BSC for change on endpoint: 0x%x state: %d\n", endpoint, state);
+
+		switch (state) {
+		case MGCP_ENDP_CRCX:
+			return MGCP_POLICY_REJECT;
+			break;
+		case MGCP_ENDP_DLCX:
+			return MGCP_POLICY_CONT;
+			break;
+		case MGCP_ENDP_MDCX:
+			return MGCP_POLICY_CONT;
+			break;
+		default:
+			LOGP(DMGCP, LOGL_FATAL, "Unhandled state: %d\n", state);
+			return MGCP_POLICY_CONT;
+			break;
+		}
+	}
+
+	/* we need to generate a new and patched message */
+	bsc_msg = bsc_mgcp_rewrite((char *) nat->mgcp_msg, nat->mgcp_length, sccp->bsc_endp,
+				   nat->mgcp_cfg->source_addr, mgcp_endp->bts_end.local_port);
+	if (!bsc_msg) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to patch the msg.\n");
+		return MGCP_POLICY_CONT;
+	}
+
+
+	bsc_endp->transaction_id = talloc_strdup(nat, transaction_id);
+	bsc_endp->transaction_state = state;
+	bsc_endp->bsc = sccp->bsc;
+
+	/* we need to update some bits */
+	if (state == MGCP_ENDP_CRCX) {
+		struct sockaddr_in sock;
+		socklen_t len = sizeof(sock);
+		if (getpeername(sccp->bsc->write_queue.bfd.fd, (struct sockaddr *) &sock, &len) != 0) {
+			LOGP(DMGCP, LOGL_ERROR, "Can not get the peername...%d/%s\n",
+			      errno, strerror(errno));
+		} else {
+			mgcp_endp->bts_end.addr = sock.sin_addr;
+		}
+
+		/* send the message and a fake MDCX to force sending of a dummy packet */
+		bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+		bsc_mgcp_send_mdcx(sccp->bsc, sccp->bsc_endp, mgcp_endp);
+		return MGCP_POLICY_DEFER;
+	} else if (state == MGCP_ENDP_DLCX) {
+		/* we will free the endpoint now and send a DLCX to the BSC */
+		msgb_free(bsc_msg);
+		bsc_mgcp_dlcx(sccp);
+		return MGCP_POLICY_CONT;
+	} else {
+		bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+		return MGCP_POLICY_DEFER;
+	}
+}
+
+/*
+ * We do have a failure, free data downstream..
+ */
+static void free_chan_downstream(struct mgcp_endpoint *endp, struct bsc_endpoint *bsc_endp,
+				 struct bsc_connection *bsc)
+{
+	LOGP(DMGCP, LOGL_ERROR, "No CI, freeing endpoint 0x%x in state %d\n",
+		ENDPOINT_NUMBER(endp), bsc_endp->transaction_state);
+
+	/* if a CRCX failed... send a DLCX down the stream */
+	if (bsc_endp->transaction_state == MGCP_ENDP_CRCX) {
+		struct sccp_connections *con;
+		con = bsc_mgcp_find_con(bsc->nat, ENDPOINT_NUMBER(endp));
+		if (!con) {
+			LOGP(DMGCP, LOGL_ERROR,
+				"No SCCP connection for endp 0x%x\n",
+				ENDPOINT_NUMBER(endp));
+		} else {
+			if (con->bsc == bsc) {
+				bsc_mgcp_send_dlcx(bsc, con->bsc_endp);
+			} else {
+				LOGP(DMGCP, LOGL_ERROR,
+					"Endpoint belongs to a different BSC\n");
+			}
+		}
+	}
+
+	bsc_mgcp_free_endpoint(bsc->nat, ENDPOINT_NUMBER(endp));
+	mgcp_free_endp(endp);
+}
+
+/*
+ * We have received a msg from the BSC. We will see if we know
+ * this transaction and if it belongs to the BSC. Then we will
+ * need to patch the content to point to the local network and we
+ * need to update the I: that was assigned by the BSS.
+ */
+void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg)
+{
+	struct msgb *output;
+	struct bsc_endpoint *bsc_endp = NULL;
+	struct mgcp_endpoint *endp = NULL;
+	int i, code;
+	char transaction_id[60];
+
+	/* Some assumption that our buffer is big enough.. and null terminate */
+	if (msgb_l2len(msg) > 2000) {
+		LOGP(DMGCP, LOGL_ERROR, "MGCP message too long.\n");
+		return;
+	}
+
+	msg->l2h[msgb_l2len(msg)] = '\0';
+
+	if (bsc_mgcp_parse_response((const char *) msg->l2h, &code, transaction_id) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to parse response code.\n");
+		return;
+	}
+
+	for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+		if (bsc->nat->bsc_endpoints[i].bsc != bsc)
+			continue;
+		/* no one listening? a bug? */
+		if (!bsc->nat->bsc_endpoints[i].transaction_id)
+			continue;
+		if (strcmp(transaction_id, bsc->nat->bsc_endpoints[i].transaction_id) != 0)
+			continue;
+
+		endp = &bsc->nat->mgcp_cfg->trunk.endpoints[i];
+		bsc_endp = &bsc->nat->bsc_endpoints[i];
+		break;
+	}
+
+	if (!bsc_endp) {
+		LOGP(DMGCP, LOGL_ERROR, "Could not find active endpoint: %s for msg: '%s'\n",
+		     transaction_id, (const char *) msg->l2h);
+		return;
+	}
+
+	endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h);
+	if (endp->ci == CI_UNUSED) {
+		free_chan_downstream(endp, bsc_endp, bsc);
+		return;
+	}
+
+	/* free some stuff */
+	talloc_free(bsc_endp->transaction_id);
+	bsc_endp->transaction_id = NULL;
+	bsc_endp->transaction_state = 0;
+
+	/*
+	 * rewrite the information. In case the endpoint was deleted
+	 * there should be nothing for us to rewrite so putting endp->rtp_port
+	 * with the value of 0 should be no problem.
+	 */
+	output = bsc_mgcp_rewrite((char * ) msg->l2h, msgb_l2len(msg), -1,
+				  bsc->nat->mgcp_cfg->source_addr, endp->net_end.local_port);
+
+	if (!output) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to rewrite MGCP msg.\n");
+		return;
+	}
+
+	if (write_queue_enqueue(&bsc->nat->mgcp_cfg->gw_fd, output) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to queue MGCP msg.\n");
+		msgb_free(output);
+	}
+}
+
+int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60])
+{
+	/* we want to parse two strings */
+	return sscanf(str, "%3d %59s\n", code, transaction) != 2;
+}
+
+uint32_t bsc_mgcp_extract_ci(const char *str)
+{
+	unsigned int ci;
+	char *res = strstr(str, "I: ");
+	if (!res) {
+		LOGP(DMGCP, LOGL_ERROR, "No CI in msg '%s'\n", str);
+		return CI_UNUSED;
+	}
+
+	if (sscanf(res, "I: %u", &ci) != 1) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to parse CI in msg '%s'\n", str);
+		return CI_UNUSED;
+	}
+
+	return ci;
+}
+
+static void patch_mgcp(struct msgb *output, const char *op, const char *tok,
+		       int endp, int len, int cr)
+{
+	int slen;
+	int ret;
+	char buf[40];
+
+	buf[0] = buf[39] = '\0';
+	ret = sscanf(tok, "%*s %s", buf);
+
+	slen = sprintf((char *) output->l3h, "%s %s %x@mgw MGCP 1.0%s",
+			op, buf, endp, cr ? "\r\n" : "\n");
+	output->l3h = msgb_put(output, slen);
+}
+
+/* we need to replace some strings... */
+struct msgb *bsc_mgcp_rewrite(char *input, int length, int endpoint, const char *ip, int port)
+{
+	static const char *crcx_str = "CRCX ";
+	static const char *dlcx_str = "DLCX ";
+	static const char *mdcx_str = "MDCX ";
+
+	static const char *ip_str = "c=IN IP4 ";
+	static const char *aud_str = "m=audio ";
+
+	char buf[128];
+	char *running, *token;
+	struct msgb *output;
+
+	if (length > 4096 - 128) {
+		LOGP(DMGCP, LOGL_ERROR, "Input is too long.\n");
+		return NULL;
+	}
+
+	output = msgb_alloc_headroom(4096, 128, "MGCP rewritten");
+	if (!output) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate new MGCP msg.\n");
+		return NULL;
+	}
+
+	running = input;
+	output->l2h = output->data;
+	output->l3h = output->l2h;
+	for (token = strsep(&running, "\n"); running; token = strsep(&running, "\n")) {
+		int len = strlen(token);
+		int cr = len > 0 && token[len - 1] == '\r';
+
+		if (strncmp(crcx_str, token, (sizeof crcx_str) - 1) == 0) {
+			patch_mgcp(output, "CRCX", token, endpoint, len, cr);
+		} else if (strncmp(dlcx_str, token, (sizeof dlcx_str) - 1) == 0) {
+			patch_mgcp(output, "DLCX", token, endpoint, len, cr);
+		} else if (strncmp(mdcx_str, token, (sizeof mdcx_str) - 1) == 0) {
+			patch_mgcp(output, "MDCX", token, endpoint, len, cr);
+		} else if (strncmp(ip_str, token, (sizeof ip_str) - 1) == 0) {
+			output->l3h = msgb_put(output, strlen(ip_str));
+			memcpy(output->l3h, ip_str, strlen(ip_str));
+			output->l3h = msgb_put(output, strlen(ip));
+			memcpy(output->l3h, ip, strlen(ip));
+
+			if (cr) {
+				output->l3h = msgb_put(output, 2);
+				output->l3h[0] = '\r';
+				output->l3h[1] = '\n';
+			} else {
+				output->l3h = msgb_put(output, 1);
+				output->l3h[0] = '\n';
+			}
+		} else if (strncmp(aud_str, token, (sizeof aud_str) - 1) == 0) {
+			int payload;
+			if (sscanf(token, "m=audio %*d RTP/AVP %d", &payload) != 1) {
+				LOGP(DMGCP, LOGL_ERROR, "Could not parsed audio line.\n");
+				msgb_free(output);
+				return NULL;
+			}
+
+			snprintf(buf, sizeof(buf)-1, "m=audio %d RTP/AVP %d%s",
+				 port, payload, cr ? "\r\n" : "\n");
+			buf[sizeof(buf)-1] = '\0';
+
+			output->l3h = msgb_put(output, strlen(buf));
+			memcpy(output->l3h, buf, strlen(buf));
+		} else {
+			output->l3h = msgb_put(output, len + 1);
+			memcpy(output->l3h, token, len);
+			output->l3h[len] = '\n';
+		}
+	}
+
+	return output;
+}
+
+static int mgcp_do_read(struct bsc_fd *fd)
+{
+	struct bsc_nat *nat;
+	struct msgb *msg, *resp;
+	int rc;
+
+	nat = fd->data;
+
+	rc = read(fd->fd, nat->mgcp_msg, sizeof(nat->mgcp_msg) - 1);
+	if (rc <= 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to read errno: %d\n", errno);
+		return -1;
+	}
+
+	nat->mgcp_msg[rc] = '\0';
+	nat->mgcp_length = rc;
+
+	msg = msgb_alloc(sizeof(nat->mgcp_msg), "MGCP GW Read");
+	if (!msg) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create buffer.\n");
+		return -1;
+	}
+
+	msg->l2h = msgb_put(msg, rc);
+	memcpy(msg->l2h, nat->mgcp_msg, msgb_l2len(msg));
+	resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+	msgb_free(msg);
+
+	/* we do have a direct answer... e.g. AUEP */
+	if (resp) {
+		if (write_queue_enqueue(&nat->mgcp_cfg->gw_fd, resp) != 0) {
+			LOGP(DMGCP, LOGL_ERROR, "Failed to enqueue msg.\n");
+			msgb_free(resp);
+		}
+	}
+
+	return 0;
+}
+
+static int mgcp_do_write(struct bsc_fd *bfd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(bfd->fd, msg->data, msg->len);
+
+	if (rc != msg->len) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to write msg to MGCP CallAgent.\n");
+		return -1;
+	}
+
+	return rc;
+}
+
+int bsc_mgcp_nat_init(struct bsc_nat *nat)
+{
+	int on;
+	struct sockaddr_in addr;
+	struct mgcp_config *cfg = nat->mgcp_cfg;
+
+	if (!cfg->call_agent_addr) {
+		LOGP(DMGCP, LOGL_ERROR, "The BSC nat requires the call agent ip to be set.\n");
+		return -1;
+	}
+
+	if (cfg->bts_ip) {
+		LOGP(DMGCP, LOGL_ERROR, "Do not set the BTS ip for the nat.\n");
+		return -1;
+	}
+
+	cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (cfg->gw_fd.bfd.fd < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to create MGCP socket. errno: %d\n", errno);
+		return -1;
+	}
+
+	on = 1;
+	setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(cfg->source_port);
+	inet_aton(cfg->source_addr, &addr.sin_addr);
+
+	if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to bind. errno: %d\n", errno);
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	addr.sin_port = htons(2727);
+	inet_aton(cfg->call_agent_addr, &addr.sin_addr);
+	if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n",
+		     cfg->call_agent_addr, errno);
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	write_queue_init(&cfg->gw_fd, 10);
+	cfg->gw_fd.bfd.when = BSC_FD_READ;
+	cfg->gw_fd.bfd.data = nat;
+	cfg->gw_fd.read_cb = mgcp_do_read;
+	cfg->gw_fd.write_cb = mgcp_do_write;
+
+	if (bsc_register_fd(&cfg->gw_fd.bfd) != 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to register MGCP fd.\n");
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	/* some more MGCP config handling */
+	cfg->data = nat;
+	cfg->policy_cb = bsc_mgcp_policy_cb;
+	cfg->trunk.force_realloc = 1;
+
+	if (cfg->bts_ip)
+		talloc_free(cfg->bts_ip);
+	cfg->bts_ip = "";
+
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       cfg->trunk.number_endpoints + 1);
+	if (!nat->bsc_endpoints) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate nat endpoints\n");
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	if (mgcp_reset_transcoder(cfg) < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to send packet to the transcoder.\n");
+		talloc_free(nat->bsc_endpoints);
+		nat->bsc_endpoints = NULL;
+		close(cfg->gw_fd.bfd.fd);
+		cfg->gw_fd.bfd.fd = -1;
+		return -1;
+	}
+
+	return 0;
+}
+
+void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc)
+{
+	struct rate_ctr *ctr = NULL;
+	int i;
+
+	if (bsc->cfg)
+		ctr = &bsc->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_CALLS];
+
+	for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+		struct bsc_endpoint *bsc_endp = &bsc->nat->bsc_endpoints[i];
+
+		if (bsc_endp->bsc != bsc)
+			continue;
+
+		if (ctr)
+			rate_ctr_inc(ctr);
+
+		bsc_mgcp_free_endpoint(bsc->nat, i);
+		mgcp_free_endp(&bsc->nat->mgcp_cfg->trunk.endpoints[i]);
+	}
+}
diff --git a/src/osmo-bsc_nat/bsc_nat.c b/src/osmo-bsc_nat/bsc_nat.c
new file mode 100644
index 0000000..643b3c4
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat.c
@@ -0,0 +1,1387 @@
+/* BSC Multiplexer/NAT */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/socket.h>
+#include <openbsc/vty.h>
+
+#include <osmocore/gsm0808.h>
+#include <osmocore/talloc.h>
+#include <osmocore/process.h>
+
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/vty.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include "../../bscconfig.h"
+
+#define SCCP_CLOSE_TIME 20
+#define SCCP_CLOSE_TIME_TIMEOUT 19
+
+struct log_target *stderr_target;
+static const char *config_file = "bsc-nat.cfg";
+static struct in_addr local_addr;
+static struct bsc_fd bsc_listen;
+static const char *msc_ip = NULL;
+static struct timer_list sccp_close;
+static int daemonize = 0;
+
+const char *openbsc_copyright =
+	"Copyright (C) 2010 Holger Hans Peter Freyther and On-Waves\r\n"
+	"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+	"This is free software: you are free to change and redistribute it.\r\n"
+	"There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static struct bsc_nat *nat;
+static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int);
+static void msc_send_reset(struct bsc_msc_connection *con);
+static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal);
+
+struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num)
+{
+	struct bsc_config *conf;
+
+	llist_for_each_entry(conf, &nat->bsc_configs, entry)
+		if (conf->nr == num)
+			return conf;
+
+	return NULL;
+}
+
+static void queue_for_msc(struct bsc_msc_connection *con, struct msgb *msg)
+{
+	if (!con) {
+		LOGP(DINP, LOGL_ERROR, "No MSC Connection assigned. Check your code.\n");
+		msgb_free(msg);
+		return;
+	}
+
+
+	if (write_queue_enqueue(&con->write_queue, msg) != 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n");
+		msgb_free(msg);
+	}
+}
+
+static void send_reset_ack(struct bsc_connection *bsc)
+{
+	static const uint8_t gsm_reset_ack[] = {
+		0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+		0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+		0x00, 0x01, 0x31,
+	};
+
+	bsc_send_data(bsc, gsm_reset_ack, sizeof(gsm_reset_ack), IPAC_PROTO_SCCP);
+}
+
+static void send_ping(struct bsc_connection *bsc)
+{
+	static const uint8_t id_ping[] = {
+		IPAC_MSGT_PING,
+	};
+
+	bsc_send_data(bsc, id_ping, sizeof(id_ping), IPAC_PROTO_IPACCESS);
+}
+
+static void send_pong(struct bsc_connection *bsc)
+{
+	static const uint8_t id_pong[] = {
+		IPAC_MSGT_PONG,
+	};
+
+	bsc_send_data(bsc, id_pong, sizeof(id_pong), IPAC_PROTO_IPACCESS);
+}
+
+static void bsc_pong_timeout(void *_bsc)
+{
+	struct bsc_connection *bsc = _bsc;
+
+	LOGP(DNAT, LOGL_ERROR, "BSC Nr: %d PONG timeout.\n", bsc->cfg->nr);
+	bsc_close_connection(bsc);
+}
+
+static void bsc_ping_timeout(void *_bsc)
+{
+	struct bsc_connection *bsc = _bsc;
+
+	if (bsc->nat->ping_timeout < 0)
+		return;
+
+	send_ping(bsc);
+
+	/* send another ping in 20 seconds */
+	bsc_schedule_timer(&bsc->ping_timeout, bsc->nat->ping_timeout, 0);
+
+	/* also start a pong timer */
+	bsc_schedule_timer(&bsc->pong_timeout, bsc->nat->pong_timeout, 0);
+}
+
+static void start_ping_pong(struct bsc_connection *bsc)
+{
+	bsc->pong_timeout.data = bsc;
+	bsc->pong_timeout.cb = bsc_pong_timeout;
+	bsc->ping_timeout.data = bsc;
+	bsc->ping_timeout.cb = bsc_ping_timeout;
+
+	bsc_ping_timeout(bsc);
+}
+
+static void send_id_ack(struct bsc_connection *bsc)
+{
+	static const uint8_t id_ack[] = {
+		IPAC_MSGT_ID_ACK
+	};
+
+	bsc_send_data(bsc, id_ack, sizeof(id_ack), IPAC_PROTO_IPACCESS);
+}
+
+static void send_id_req(struct bsc_connection *bsc)
+{
+	static const uint8_t id_req[] = {
+		IPAC_MSGT_ID_GET,
+		0x01, IPAC_IDTAG_UNIT,
+		0x01, IPAC_IDTAG_MACADDR,
+		0x01, IPAC_IDTAG_LOCATION1,
+		0x01, IPAC_IDTAG_LOCATION2,
+		0x01, IPAC_IDTAG_EQUIPVERS,
+		0x01, IPAC_IDTAG_SWVERSION,
+		0x01, IPAC_IDTAG_UNITNAME,
+		0x01, IPAC_IDTAG_SERNR,
+	};
+
+	bsc_send_data(bsc, id_req, sizeof(id_req), IPAC_PROTO_IPACCESS);
+}
+
+static void nat_send_rlsd_msc(struct sccp_connections *conn)
+{
+	struct sccp_connection_released *rel;
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "rlsd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(*rel));
+	rel = (struct sccp_connection_released *) msg->l2h;
+	rel->type = SCCP_MSG_TYPE_RLSD;
+	rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE;
+	rel->destination_local_reference = conn->remote_ref;
+	rel->source_local_reference = conn->patched_ref;
+
+	ipaccess_prepend_header(msg, IPAC_PROTO_SCCP);
+
+	queue_for_msc(conn->msc_con, msg);
+}
+
+static void nat_send_rlsd_bsc(struct sccp_connections *conn)
+{
+	struct sccp_connection_released *rel;
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "rlsd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(*rel));
+	rel = (struct sccp_connection_released *) msg->l2h;
+	rel->type = SCCP_MSG_TYPE_RLSD;
+	rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE;
+	rel->destination_local_reference = conn->real_ref;
+	rel->source_local_reference = conn->remote_ref;
+
+	bsc_write(conn->bsc, msg, IPAC_PROTO_SCCP);
+}
+
+static struct msgb *nat_creat_clrc(struct sccp_connections *conn, uint8_t cause)
+{
+	struct msgb *msg;
+	struct msgb *sccp;
+
+	msg = gsm0808_create_clear_command(cause);
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+		return NULL;
+	}
+
+	sccp = sccp_create_dt1(&conn->real_ref, msg->data, msg->len);
+	if (!sccp) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate SCCP msg.\n");
+		msgb_free(msg);
+		return NULL;
+	}
+
+	msgb_free(msg);
+	return sccp;
+}
+
+static int nat_send_clrc_bsc(struct sccp_connections *conn)
+{
+	struct msgb *sccp;
+
+	sccp = nat_creat_clrc(conn, 0x20);
+	if (!sccp)
+		return -1;
+	return bsc_write(conn->bsc, sccp, IPAC_PROTO_SCCP);
+}
+
+static void nat_send_rlc(struct bsc_msc_connection *msc_con,
+			 struct sccp_source_reference *src,
+			 struct sccp_source_reference *dst)
+{
+	struct sccp_connection_release_complete *rlc;
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "rlc");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(*rlc));
+	rlc = (struct sccp_connection_release_complete *) msg->l2h;
+	rlc->type = SCCP_MSG_TYPE_RLC;
+	rlc->destination_local_reference = *dst;
+	rlc->source_local_reference = *src;
+
+	ipaccess_prepend_header(msg, IPAC_PROTO_SCCP);
+
+	queue_for_msc(msc_con, msg);
+}
+
+static void send_mgcp_reset(struct bsc_connection *bsc)
+{
+	static const uint8_t mgcp_reset[] = {
+	    "RSIP 1 13@mgw MGCP 1.0\r\n"
+	};
+
+	bsc_write_mgcp(bsc, mgcp_reset, sizeof mgcp_reset - 1);
+}
+
+/*
+ * Below is the handling of messages coming
+ * from the MSC and need to be forwarded to
+ * a real BSC.
+ */
+static void initialize_msc_if_needed(struct bsc_msc_connection *msc_con)
+{
+	if (msc_con->first_contact)
+		return;
+
+	msc_con->first_contact = 1;
+	msc_send_reset(msc_con);
+}
+
+static void send_id_get_response(struct bsc_msc_connection *msc_con)
+{
+	struct msgb *msg = bsc_msc_id_get_resp(nat->token);
+	if (!msg)
+		return;
+
+	ipaccess_prepend_header(msg, IPAC_PROTO_IPACCESS);
+	queue_for_msc(msc_con, msg);
+}
+
+/*
+ * Currently we are lacking refcounting so we need to copy each message.
+ */
+static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int proto)
+{
+	struct msgb *msg;
+
+	if (length > 4096 - 128) {
+		LOGP(DINP, LOGL_ERROR, "Can not send message of that size.\n");
+		return;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "to-bsc");
+	if (!msg) {
+		LOGP(DINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, length);
+	memcpy(msg->data, data, length);
+
+	bsc_write(bsc, msg, proto);
+}
+
+/*
+ * Update the release statistics
+ */
+static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal)
+{
+	if (!bsc->cfg) {
+		LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.");
+		return;
+	}
+
+	if (filter >= 0) {
+		LOGP(DNAT, LOGL_ERROR, "Connection was not rejected");
+		return;
+	}
+
+	if (filter == -1)
+		rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_ILL_PACKET]);
+	else if (normal)
+		rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_MSG]);
+	else
+		rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_CR]);
+}
+
+/*
+ * Release an established connection. We will have to release it to the BSC
+ * and to the network and we do it the following way.
+ * 1.) Give up on the MSC side
+ *  1.1) Send a RLSD message, it is a bit non standard but should work, we
+ *       ignore the RLC... we might complain about it. Other options would
+ *       be to send a Release Request, handle the Release Complete..
+ *  1.2) Mark the data structure to be con_local and wait for 2nd
+ *
+ * 2.) Give up on the BSC side
+ *  2.1) Depending on the con type reject the service, or just close it
+ */
+static void bsc_send_con_release(struct bsc_connection *bsc, struct sccp_connections *con)
+{
+	struct msgb *rlsd;
+	/* 1. release the network */
+	rlsd = sccp_create_rlsd(&con->patched_ref, &con->remote_ref,
+				SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+	if (!rlsd)
+		LOGP(DNAT, LOGL_ERROR, "Failed to create RLSD message.\n");
+	else {
+		ipaccess_prepend_header(rlsd, IPAC_PROTO_SCCP);
+		queue_for_msc(con->msc_con, rlsd);
+	}
+	con->con_local = 1;
+	con->msc_con = NULL;
+
+	/* 2. release the BSC side */
+	if (con->con_type == NAT_CON_TYPE_LU) {
+		struct msgb *payload, *udt;
+		payload = gsm48_create_loc_upd_rej(GSM48_REJECT_PLMN_NOT_ALLOWED);
+
+		if (payload) {
+			gsm0808_prepend_dtap_header(payload, 0);
+			udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+			if (udt)
+				bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+			else
+				LOGP(DNAT, LOGL_ERROR, "Failed to create DT1\n");
+
+			msgb_free(payload);
+		} else {
+			LOGP(DNAT, LOGL_ERROR, "Failed to allocate LU Reject.\n");
+		}
+	}
+
+	nat_send_clrc_bsc(con);
+
+	rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+				SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+	if (!rlsd) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate RLSD for the BSC.\n");
+		sccp_connection_destroy(con);
+		return;
+	}
+
+	con->con_type = NAT_CON_TYPE_LOCAL_REJECT;
+	bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+}
+
+static void bsc_send_con_refuse(struct bsc_connection *bsc,
+				struct bsc_nat_parsed *parsed, int con_type)
+{
+	struct msgb *payload;
+	struct msgb *refuse;
+
+	if (con_type == NAT_CON_TYPE_LU)
+		payload = gsm48_create_loc_upd_rej(GSM48_REJECT_PLMN_NOT_ALLOWED);
+	else if (con_type == NAT_CON_TYPE_CM_SERV_REQ)
+		payload = gsm48_create_mm_serv_rej(GSM48_REJECT_PLMN_NOT_ALLOWED);
+	else {
+		LOGP(DNAT, LOGL_ERROR, "Unknown connection type: %d\n", con_type);
+		payload = NULL;
+	}
+
+	/*
+	 * Some BSCs do not handle the payload inside a SCCP CREF msg
+	 * so we will need to:
+	 * 1.) Allocate a local connection and mark it as local..
+	 * 2.) queue data for downstream.. and the RLC should delete everything
+	 */
+	if (payload) {
+		struct msgb *cc, *udt, *clear, *rlsd;
+		struct sccp_connections *con;
+		con = create_sccp_src_ref(bsc, parsed);
+		if (!con)
+			goto send_refuse;
+
+		/* declare it local and assign a unique remote_ref */
+		con->con_type = NAT_CON_TYPE_LOCAL_REJECT;
+		con->con_local = 1;
+		con->has_remote_ref = 1;
+		con->remote_ref = con->patched_ref;
+
+		/* 1. create a confirmation */
+		cc = sccp_create_cc(&con->remote_ref, &con->real_ref);
+		if (!cc)
+			goto send_refuse;
+
+		/* 2. create the DT1 */
+		gsm0808_prepend_dtap_header(payload, 0);
+		udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+		if (!udt) {
+			msgb_free(cc);
+			goto send_refuse;
+		}
+
+		/* 3. send a Clear Command */
+		clear = nat_creat_clrc(con, 0x20);
+		if (!clear) {
+			msgb_free(cc);
+			msgb_free(udt);
+			goto send_refuse;
+		}
+
+		/* 4. send a RLSD */
+		rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+					SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+		if (!rlsd) {
+			msgb_free(cc);
+			msgb_free(udt);
+			msgb_free(clear);
+			goto send_refuse;
+		}
+
+		bsc_write(bsc, cc, IPAC_PROTO_SCCP);
+		bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+		bsc_write(bsc, clear, IPAC_PROTO_SCCP);
+		bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+		msgb_free(payload);
+		return;
+	}
+
+
+send_refuse:
+	if (payload)
+		msgb_free(payload);
+
+	refuse = sccp_create_refuse(parsed->src_local_ref,
+				    SCCP_REFUSAL_SCCP_FAILURE, NULL, 0);
+	if (!refuse) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "Creating refuse msg failed for SCCP 0x%x on BSC Nr: %d.\n",
+		      sccp_src_ref_to_int(parsed->src_local_ref), bsc->cfg->nr);
+		return;
+	}
+
+	bsc_write(bsc, refuse, IPAC_PROTO_SCCP);
+}
+
+
+static int forward_sccp_to_bts(struct bsc_msc_connection *msc_con, struct msgb *msg)
+{
+	struct sccp_connections *con = NULL;
+	struct bsc_connection *bsc;
+	struct bsc_nat_parsed *parsed;
+	int proto;
+
+	/* filter, drop, patch the message? */
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
+		return -1;
+	}
+
+	if (bsc_nat_filter_ipa(DIR_BSC, msg, parsed))
+		goto exit;
+
+	proto = parsed->ipa_proto;
+
+	/* Route and modify the SCCP packet */
+	if (proto == IPAC_PROTO_SCCP) {
+		switch (parsed->sccp_type) {
+		case SCCP_MSG_TYPE_UDT:
+			/* forward UDT messages to every BSC */
+			goto send_to_all;
+			break;
+		case SCCP_MSG_TYPE_RLSD:
+		case SCCP_MSG_TYPE_CREF:
+		case SCCP_MSG_TYPE_DT1:
+		case SCCP_MSG_TYPE_IT:
+			con = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+			if (parsed->gsm_type == BSS_MAP_MSG_ASSIGMENT_RQST) {
+				counter_inc(nat->stats.sccp.calls);
+
+				if (con) {
+					struct rate_ctr_group *ctrg;
+					ctrg = con->bsc->cfg->stats.ctrg;
+					rate_ctr_inc(&ctrg->ctr[BCFG_CTR_SCCP_CALLS]);
+					if (bsc_mgcp_assign_patch(con, msg) != 0)
+						LOGP(DNAT, LOGL_ERROR, "Failed to assign...\n");
+				} else
+					LOGP(DNAT, LOGL_ERROR, "Assignment command but no BSC.\n");
+			}
+			break;
+		case SCCP_MSG_TYPE_CC:
+			con = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+			if (!con || update_sccp_src_ref(con, parsed) != 0)
+				goto exit;
+			break;
+		case SCCP_MSG_TYPE_RLC:
+			LOGP(DNAT, LOGL_ERROR, "Unexpected release complete from MSC.\n");
+			goto exit;
+			break;
+		case SCCP_MSG_TYPE_CR:
+			/* MSC never opens a SCCP connection, fall through */
+		default:
+			goto exit;
+		}
+
+		if (!con && parsed->sccp_type == SCCP_MSG_TYPE_RLSD) {
+			LOGP(DNAT, LOGL_NOTICE, "Sending fake RLC on RLSD message to network.\n");
+			/* Exchange src/dest for the reply */
+			nat_send_rlc(msc_con, parsed->dest_local_ref, parsed->src_local_ref);
+		} else if (!con)
+			LOGP(DNAT, LOGL_ERROR, "Unknown connection for msg type: 0x%x from the MSC.\n", parsed->sccp_type);
+	}
+
+	talloc_free(parsed);
+	if (!con)
+		return -1;
+	if (!con->bsc->authenticated) {
+		LOGP(DNAT, LOGL_ERROR, "Selected BSC not authenticated.\n");
+		return -1;
+	}
+
+	bsc_send_data(con->bsc, msg->l2h, msgb_l2len(msg), proto);
+	return 0;
+
+send_to_all:
+	/*
+	 * Filter Paging from the network. We do not want to send a PAGING
+	 * Command to every BSC in our network. We will analys the PAGING
+	 * message and then send it to the authenticated messages...
+	 */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP && parsed->gsm_type == BSS_MAP_MSG_PAGING) {
+		int lac;
+		bsc = bsc_nat_find_bsc(nat, msg, &lac);
+		if (bsc && bsc->cfg->forbid_paging)
+			LOGP(DNAT, LOGL_DEBUG, "Paging forbidden for BTS: %d\n", bsc->cfg->nr);
+		else if (bsc)
+			bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto);
+		else if (lac != -1)
+			LOGP(DNAT, LOGL_ERROR, "Could not determine BSC for paging on lac: %d/0x%x\n",
+			     lac, lac);
+
+		goto exit;
+	}
+	/* currently send this to every BSC connected */
+	llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+		if (!bsc->authenticated)
+			continue;
+
+		bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto);
+	}
+
+exit:
+	talloc_free(parsed);
+	return 0;
+}
+
+static void msc_connection_was_lost(struct bsc_msc_connection *con)
+{
+	struct bsc_connection *bsc, *tmp;
+
+	LOGP(DMSC, LOGL_ERROR, "Closing all connections downstream.\n");
+	llist_for_each_entry_safe(bsc, tmp, &nat->bsc_connections, list_entry)
+		bsc_close_connection(bsc);
+
+	bsc_mgcp_free_endpoints(nat);
+	bsc_msc_schedule_connect(con);
+}
+
+static void msc_connection_connected(struct bsc_msc_connection *con)
+{
+	counter_inc(nat->stats.msc.reconn);
+}
+
+static void msc_send_reset(struct bsc_msc_connection *msc_con)
+{
+	static const uint8_t reset[] = {
+		0x00, 0x12, 0xfd,
+		0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+		0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+		0x01, 0x20
+	};
+
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "08.08 reset");
+	if (!msg) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to allocate reset msg.\n");
+		return;
+	}
+
+	msg->l2h = msgb_put(msg, sizeof(reset));
+	memcpy(msg->l2h, reset, msgb_l2len(msg));
+
+	queue_for_msc(msc_con, msg);
+
+	LOGP(DMSC, LOGL_NOTICE, "Scheduled GSM0808 reset msg for the MSC.\n");
+}
+
+static int ipaccess_msc_read_cb(struct bsc_fd *bfd)
+{
+	int error;
+	struct bsc_msc_connection *msc_con;
+	struct msgb *msg = ipaccess_read_msg(bfd, &error);
+	struct ipaccess_head *hh;
+
+	msc_con = (struct bsc_msc_connection *) bfd->data;
+
+	if (!msg) {
+		if (error == 0)
+			LOGP(DNAT, LOGL_FATAL, "The connection the MSC was lost, exiting\n");
+		else
+			LOGP(DNAT, LOGL_ERROR, "Failed to parse ip access message: %d\n", error);
+
+		bsc_msc_lost(msc_con);
+		return -1;
+	}
+
+	LOGP(DNAT, LOGL_DEBUG, "MSG from MSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]);
+
+	/* handle base message handling */
+	hh = (struct ipaccess_head *) msg->data;
+	ipaccess_rcvmsg_base(msg, bfd);
+
+	/* initialize the networking. This includes sending a GSM08.08 message */
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_ID_ACK)
+			initialize_msc_if_needed(msc_con);
+		else if (msg->l2h[0] == IPAC_MSGT_ID_GET)
+			send_id_get_response(msc_con);
+	} else if (hh->proto == IPAC_PROTO_SCCP)
+		forward_sccp_to_bts(msc_con, msg);
+
+	msgb_free(msg);
+	return 0;
+}
+
+static int ipaccess_msc_write_cb(struct bsc_fd *bfd, struct msgb *msg)
+{
+	int rc;
+	rc = write(bfd->fd, msg->data, msg->len);
+
+	if (rc != msg->len) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to write MSG to MSC.\n");
+		return -1;
+	}
+
+	return rc;
+}
+
+/*
+ * Below is the handling of messages coming
+ * from the BSC and need to be forwarded to
+ * a real BSC.
+ */
+
+/*
+ * Remove the connection from the connections list,
+ * remove it from the patching of SCCP header lists
+ * as well. Maybe in the future even close connection..
+ */
+void bsc_close_connection(struct bsc_connection *connection)
+{
+	struct sccp_connections *sccp_patch, *tmp;
+	struct rate_ctr *ctr = NULL;
+
+	/* stop the timeout timer */
+	bsc_del_timer(&connection->id_timeout);
+	bsc_del_timer(&connection->ping_timeout);
+	bsc_del_timer(&connection->pong_timeout);
+
+	if (connection->cfg)
+		ctr = &connection->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_SCCP];
+
+	/* remove all SCCP connections */
+	llist_for_each_entry_safe(sccp_patch, tmp, &nat->sccp_connections, list_entry) {
+		if (sccp_patch->bsc != connection)
+			continue;
+
+		if (ctr)
+			rate_ctr_inc(ctr);
+		if (sccp_patch->has_remote_ref && !sccp_patch->con_local)
+			nat_send_rlsd_msc(sccp_patch);
+		sccp_connection_destroy(sccp_patch);
+	}
+
+	/* close endpoints allocated by this BSC */
+	bsc_mgcp_clear_endpoints_for(connection);
+
+	bsc_unregister_fd(&connection->write_queue.bfd);
+	close(connection->write_queue.bfd.fd);
+	write_queue_clear(&connection->write_queue);
+	llist_del(&connection->list_entry);
+
+	talloc_free(connection);
+}
+
+static void ipaccess_close_bsc(void *data)
+{
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+	struct bsc_connection *conn = data;
+
+
+	getpeername(conn->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+	LOGP(DNAT, LOGL_ERROR, "BSC on %s didn't respond to identity request. Closing.\n",
+	     inet_ntoa(sock.sin_addr));
+	bsc_close_connection(conn);
+}
+
+static void ipaccess_auth_bsc(struct tlv_parsed *tvp, struct bsc_connection *bsc)
+{
+	struct bsc_config *conf;
+	const char *token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+	const int len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+
+	if (bsc->cfg) {
+		LOGP(DNAT, LOGL_ERROR, "Reauth on fd %d bsc nr %d\n",
+		     bsc->write_queue.bfd.fd, bsc->cfg->nr);
+		return;
+	}
+
+	llist_for_each_entry(conf, &bsc->nat->bsc_configs, entry) {
+		if (strncmp(conf->token, token, len) == 0) {
+			rate_ctr_inc(&conf->stats.ctrg->ctr[BCFG_CTR_NET_RECONN]);
+			bsc->authenticated = 1;
+			bsc->cfg = conf;
+			bsc_del_timer(&bsc->id_timeout);
+			LOGP(DNAT, LOGL_NOTICE, "Authenticated bsc nr: %d on fd %d\n",
+			     conf->nr, bsc->write_queue.bfd.fd);
+			start_ping_pong(bsc);
+			return;
+		}
+	}
+
+	LOGP(DNAT, LOGL_ERROR, "No bsc found for token %s on fd: %d.\n", token,
+	     bsc->write_queue.bfd.fd);
+}
+
+static void handle_con_stats(struct sccp_connections *con)
+{
+	struct rate_ctr_group *ctrg;
+	int id = bsc_conn_type_to_ctr(con);
+
+	if (id == -1)
+		return;
+
+	if (!con->bsc || !con->bsc->cfg)
+		return;
+
+	ctrg = con->bsc->cfg->stats.ctrg;
+	rate_ctr_inc(&ctrg->ctr[id]);
+}
+
+static int forward_sccp_to_msc(struct bsc_connection *bsc, struct msgb *msg)
+{
+	int con_filter = 0;
+	char *imsi = NULL;
+	struct bsc_msc_connection *con_msc = NULL;
+	struct bsc_connection *con_bsc = NULL;
+	int con_type;
+	struct bsc_nat_parsed *parsed;
+
+	/* Parse and filter messages */
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	if (bsc_nat_filter_ipa(DIR_MSC, msg, parsed))
+		goto exit;
+
+	/*
+	 * check authentication after filtering to not reject auth
+	 * responses coming from the BSC. We have to make sure that
+	 * nothing from the exit path will forward things to the MSC
+	 */
+	if (!bsc->authenticated) {
+		LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+
+	/* modify the SCCP entries */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+		int filter;
+		struct sccp_connections *con;
+		switch (parsed->sccp_type) {
+		case SCCP_MSG_TYPE_CR:
+			filter = bsc_nat_filter_sccp_cr(bsc, msg, parsed, &con_type, &imsi);
+			if (filter < 0) {
+				bsc_stat_reject(filter, bsc, 0);
+				goto exit3;
+			}
+
+			if (!create_sccp_src_ref(bsc, parsed))
+				goto exit2;
+			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+			con->msc_con = bsc->nat->msc_con;
+			con_msc = con->msc_con;
+			con->con_type = con_type;
+			con->imsi_checked = filter;
+			if (imsi)
+				con->imsi = talloc_steal(con, imsi);
+			imsi = NULL;
+			con_bsc = con->bsc;
+			handle_con_stats(con);
+			break;
+		case SCCP_MSG_TYPE_RLSD:
+		case SCCP_MSG_TYPE_CREF:
+		case SCCP_MSG_TYPE_DT1:
+		case SCCP_MSG_TYPE_CC:
+		case SCCP_MSG_TYPE_IT:
+			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+			if (con) {
+				/* only filter non local connections */
+				if (!con->con_local) {
+					filter = bsc_nat_filter_dt(bsc, msg, con, parsed);
+					if (filter < 0) {
+						bsc_stat_reject(filter, bsc, 1);
+						bsc_send_con_release(bsc, con);
+						con = NULL;
+						goto exit2;
+					}
+
+					/* hand data to a side channel */
+					if (bsc_check_ussd(con, parsed, msg) == 1) 
+						con->con_local = 2;
+
+					/*
+					 * Optionally rewrite setup message. This can
+					 * replace the msg and the parsed structure becomes
+					 * invalid.
+					 */
+					msg = bsc_nat_rewrite_setup(bsc->nat, msg, parsed, con->imsi);
+					talloc_free(parsed);
+					parsed = NULL;
+				}
+
+				con_bsc = con->bsc;
+				con_msc = con->msc_con;
+				con_filter = con->con_local;
+			}
+
+			break;
+		case SCCP_MSG_TYPE_RLC:
+			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+			if (con) {
+				con_bsc = con->bsc;
+				con_msc = con->msc_con;
+				con_filter = con->con_local;
+			}
+			remove_sccp_src_ref(bsc, msg, parsed);
+			break;
+		case SCCP_MSG_TYPE_UDT:
+			/* simply forward everything */
+			con = NULL;
+			break;
+		default:
+			LOGP(DNAT, LOGL_ERROR, "Not forwarding to msc sccp type: 0x%x\n", parsed->sccp_type);
+			con = NULL;
+			goto exit2;
+			break;
+		}
+        } else if (parsed->ipa_proto == IPAC_PROTO_MGCP_OLD) {
+                bsc_mgcp_forward(bsc, msg);
+                goto exit2;
+	} else {
+		LOGP(DNAT, LOGL_ERROR, "Not forwarding unknown stream id: 0x%x\n", parsed->ipa_proto);
+		goto exit2;
+	}
+
+	if (con_msc && con_bsc != bsc) {
+		LOGP(DNAT, LOGL_ERROR, "The connection belongs to a different BTS: input: %d con: %d\n",
+		     bsc->cfg->nr, con_bsc->cfg->nr);
+		goto exit2;
+	}
+
+	/* do not forward messages to the MSC */
+	if (con_filter)
+		goto exit2;
+
+	if (!con_msc) {
+		LOGP(DNAT, LOGL_ERROR, "Not forwarding data bsc_nr: %d ipa: %d type: 0x%x\n",
+			bsc->cfg->nr,
+			parsed ? parsed->ipa_proto : -1,
+			parsed ? parsed->sccp_type : -1);
+		goto exit2;
+	}
+
+	/* send the non-filtered but maybe modified msg */
+	queue_for_msc(con_msc, msg);
+	if (parsed)
+		talloc_free(parsed);
+	return 0;
+
+exit:
+	/* if we filter out the reset send an ack to the BSC */
+	if (parsed->bssap == 0 && parsed->gsm_type == BSS_MAP_MSG_RESET) {
+		send_reset_ack(bsc);
+		send_reset_ack(bsc);
+	} else if (parsed->ipa_proto == IPAC_PROTO_IPACCESS) {
+		/* do we know who is handling this? */
+		if (msg->l2h[0] == IPAC_MSGT_ID_RESP) {
+			struct tlv_parsed tvp;
+			ipaccess_idtag_parse(&tvp,
+					     (unsigned char *) msg->l2h + 2,
+					     msgb_l2len(msg) - 2);
+			if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME))
+				ipaccess_auth_bsc(&tvp, bsc);
+		}
+
+		goto exit2;
+	}
+
+exit2:
+	if (imsi)
+		talloc_free(imsi);
+	talloc_free(parsed);
+	msgb_free(msg);
+	return -1;
+
+exit3:
+	/* send a SCCP Connection Refused */
+	if (imsi)
+		talloc_free(imsi);
+	bsc_send_con_refuse(bsc, parsed, con_type);
+	talloc_free(parsed);
+	msgb_free(msg);
+	return -1;
+}
+
+static int ipaccess_bsc_read_cb(struct bsc_fd *bfd)
+{
+	int error;
+	struct bsc_connection *bsc = bfd->data;
+	struct msgb *msg = ipaccess_read_msg(bfd, &error);
+	struct ipaccess_head *hh;
+
+	if (!msg) {
+		if (error == 0)
+			LOGP(DNAT, LOGL_ERROR,
+			     "The connection to the BSC Nr: %d was lost. Cleaning it\n",
+			     bsc->cfg ? bsc->cfg->nr : -1);
+		else
+			LOGP(DNAT, LOGL_ERROR,
+			     "Stream error on BSC Nr: %d. Failed to parse ip access message: %d\n",
+			     bsc->cfg ? bsc->cfg->nr : -1, error);
+
+		bsc_close_connection(bsc);
+		return -1;
+	}
+
+
+	LOGP(DNAT, LOGL_DEBUG, "MSG from BSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]);
+
+	/* Handle messages from the BSC */
+	hh = (struct ipaccess_head *) msg->data;
+
+	/* stop the pong timeout */
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_PONG) {
+			bsc_del_timer(&bsc->pong_timeout);
+			msgb_free(msg);
+			return 0;
+		} else if (msg->l2h[0] == IPAC_MSGT_PING) {
+			send_pong(bsc);
+			msgb_free(msg);
+			return 0;
+		}
+	}
+
+	/* FIXME: Currently no PONG is sent to the BSC */
+	/* FIXME: Currently no ID ACK is sent to the BSC */
+	forward_sccp_to_msc(bsc, msg);
+
+	return 0;
+}
+
+static int ipaccess_listen_bsc_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct bsc_connection *bsc;
+	int fd, rc, on;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		perror("accept");
+		return fd;
+	}
+
+	/* count the reconnect */
+	counter_inc(nat->stats.bsc.reconn);
+
+	/*
+	 * if we are not connected to a msc... just close the socket
+	 */
+	if (!bsc_nat_msc_is_connected(nat)) {
+		LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due lack of MSC connection.\n");
+		close(fd);
+		return 0;
+	}
+
+	on = 1;
+	rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+	if (rc != 0)
+                LOGP(DNAT, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
+
+	rc = setsockopt(fd, IPPROTO_IP, IP_TOS,
+			&nat->bsc_ip_dscp, sizeof(nat->bsc_ip_dscp));
+	if (rc != 0)
+		LOGP(DNAT, LOGL_ERROR, "Failed to set IP_TOS: %s\n", strerror(errno));
+
+	/* todo... do something with the connection */
+	/* todo... use GNUtls to see if we want to trust this as a BTS */
+
+	/*
+	 *
+	 */
+	bsc = bsc_connection_alloc(nat);
+	if (!bsc) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate BSC struct.\n");
+		close(fd);
+		return -1;
+	}
+
+	bsc->write_queue.bfd.data = bsc;
+	bsc->write_queue.bfd.fd = fd;
+	bsc->write_queue.read_cb = ipaccess_bsc_read_cb;
+	bsc->write_queue.write_cb = bsc_write_cb;
+	bsc->write_queue.bfd.when = BSC_FD_READ;
+	if (bsc_register_fd(&bsc->write_queue.bfd) < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to register BSC fd.\n");
+		close(fd);
+		talloc_free(bsc);
+		return -2;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "BSC connection on %d with IP: %s\n",
+		fd, inet_ntoa(sa.sin_addr));
+	llist_add(&bsc->list_entry, &nat->bsc_connections);
+	send_id_ack(bsc);
+	send_id_req(bsc);
+	send_mgcp_reset(bsc);
+
+	/*
+	 * start the hangup timer
+	 */
+	bsc->id_timeout.data = bsc;
+	bsc->id_timeout.cb = ipaccess_close_bsc;
+	bsc_schedule_timer(&bsc->id_timeout, nat->auth_timeout, 0);
+	return 0;
+}
+
+static void print_usage()
+{
+	printf("Usage: bsc_nat\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+	printf("  -D --daemonize Fork the process into a background daemon\n");
+	printf("  -s --disable-color\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -m --msc=IP. The address of the MSC.\n");
+	printf("  -l --local=IP. The local address of this BSC.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"timestamp", 0, 0, 'T'},
+			{"msc", 1, 0, 'm'},
+			{"local", 1, 0, 'l'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:sTPc:m:l:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 'c':
+			config_file = strdup(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'm':
+			msc_ip = optarg;
+			break;
+		case 'l':
+			inet_aton(optarg, &local_addr);
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+static void signal_handler(int signal)
+{
+	switch (signal) {
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+static void sccp_close_unconfirmed(void *_data)
+{
+	struct sccp_connections *conn, *tmp1;
+	struct timespec now;
+	clock_gettime(CLOCK_MONOTONIC, &now);
+
+	llist_for_each_entry_safe(conn, tmp1, &nat->sccp_connections, list_entry) {
+		if (conn->has_remote_ref)
+			continue;
+
+		int diff = (now.tv_sec - conn->creation_time.tv_sec) / 60;
+		if (diff < SCCP_CLOSE_TIME_TIMEOUT)
+			continue;
+
+		LOGP(DNAT, LOGL_ERROR, "SCCP connection 0x%x/0x%x was never confirmed.\n",
+		     sccp_src_ref_to_int(&conn->real_ref),
+		     sccp_src_ref_to_int(&conn->patched_ref));
+		sccp_connection_destroy(conn);
+	}
+
+	bsc_schedule_timer(&sccp_close, SCCP_CLOSE_TIME, 0);
+}
+
+extern void *tall_msgb_ctx;
+extern void *tall_ctr_ctx;
+static void talloc_init_ctx()
+{
+	tall_bsc_ctx = talloc_named_const(NULL, 0, "nat");
+	tall_msgb_ctx = talloc_named_const(tall_bsc_ctx, 0, "msgb");
+	tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
+}
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OsmoBSCNAT",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	talloc_init_ctx();
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	nat = bsc_nat_alloc();
+	if (!nat) {
+		fprintf(stderr, "Failed to allocate the BSC nat.\n");
+		return -4;
+	}
+
+	nat->mgcp_cfg = mgcp_config_alloc();
+	if (!nat->mgcp_cfg) {
+		fprintf(stderr, "Failed to allocate MGCP cfg.\n");
+		return -5;
+	}
+
+	vty_info.copyright = openbsc_copyright;
+	vty_init(&vty_info);
+	logging_vty_add_cmds();
+	bsc_nat_vty_init(nat);
+
+
+	/* parse options */
+	local_addr.s_addr = INADDR_ANY;
+	handle_options(argc, argv);
+
+	rate_ctr_init(tall_bsc_ctx);
+
+	/* init vty and parse */
+	telnet_init(tall_bsc_ctx, NULL, 4244);
+	if (mgcp_parse_config(config_file, nat->mgcp_cfg) < 0) {
+		fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+		return -3;
+	}
+
+	/* over rule the VTY config */
+	if (msc_ip)
+		bsc_nat_set_msc_ip(nat, msc_ip);
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	/*
+	 * Setup the MGCP code..
+	 */
+	if (bsc_mgcp_nat_init(nat) != 0)
+		return -4;
+
+	/* connect to the MSC */
+	nat->msc_con = bsc_msc_create(nat->msc_ip, nat->msc_port, 0);
+	if (!nat->msc_con) {
+		fprintf(stderr, "Creating a bsc_msc_connection failed.\n");
+		exit(1);
+	}
+
+	nat->msc_con->connection_loss = msc_connection_was_lost;
+	nat->msc_con->connected = msc_connection_connected;
+	nat->msc_con->write_queue.read_cb = ipaccess_msc_read_cb;
+	nat->msc_con->write_queue.write_cb = ipaccess_msc_write_cb;;
+	nat->msc_con->write_queue.bfd.data = nat->msc_con;
+	bsc_msc_connect(nat->msc_con);
+
+	/* wait for the BSC */
+	rc = make_sock(&bsc_listen, IPPROTO_TCP, ntohl(local_addr.s_addr),
+		       5000, ipaccess_listen_bsc_cb);
+	if (rc != 0) {
+		fprintf(stderr, "Failed to listen for BSC.\n");
+		exit(1);
+	}
+
+	rc = bsc_ussd_init(nat);
+	if (rc != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to bind the USSD socket.\n");
+		exit(1);
+	}
+
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGPIPE, SIG_IGN);
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	/* recycle timer */
+	sccp_set_log_area(DSCCP);
+	sccp_close.cb = sccp_close_unconfirmed;
+	sccp_close.data = NULL;
+	bsc_schedule_timer(&sccp_close, SCCP_CLOSE_TIME, 0);
+
+	while (1) {
+		bsc_select_main(0);
+	}
+
+	return 0;
+}
+
+/* Close all connections handed out to the USSD module */
+int bsc_close_ussd_connections(struct bsc_nat *nat)
+{
+	struct sccp_connections *con;
+	llist_for_each_entry(con, &nat->sccp_connections, list_entry) {
+		if (con->con_local != 2)
+			continue;
+		if (!con->bsc)
+			continue;
+
+		nat_send_clrc_bsc(con);
+		nat_send_rlsd_bsc(con);
+	}
+
+	return 0;
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_utils.c b/src/osmo-bsc_nat/bsc_nat_utils.c
new file mode 100644
index 0000000..cd294cc
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_utils.c
@@ -0,0 +1,893 @@
+
+/* BSC Multiplexer/NAT Utilities */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/vty.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/gsm0808.h>
+
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+static const struct rate_ctr_desc bsc_cfg_ctr_description[] = {
+	[BCFG_CTR_SCCP_CONN]     = { "sccp.conn",      "SCCP Connections         "},
+	[BCFG_CTR_SCCP_CALLS]    = { "sccp.calls",     "SCCP Assignment Commands "},
+	[BCFG_CTR_NET_RECONN]    = { "net.reconnects", "Network reconnects       "},
+	[BCFG_CTR_DROPPED_SCCP]  = { "dropped.sccp",   "Dropped SCCP connections."},
+	[BCFG_CTR_DROPPED_CALLS] = { "dropped.calls",  "Dropped active calls.    "},
+	[BCFG_CTR_REJECTED_CR]   = { "rejected.cr",    "Rejected CR due filter   "},
+	[BCFG_CTR_REJECTED_MSG]  = { "rejected.msg",   "Rejected MSG due filter  "},
+	[BCFG_CTR_ILL_PACKET]    = { "rejected.ill",   "Rejected due parse error "},
+	[BCFG_CTR_CON_TYPE_LU]   = { "conn.lu",        "Conn Location Update     "},
+	[BCFG_CTR_CON_CMSERV_RQ] = { "conn.rq",        "Conn CM Service Req      "},
+	[BCFG_CTR_CON_PAG_RESP]  = { "conn.pag",       "Conn Paging Response     "},
+	[BCFG_CTR_CON_SSA]       = { "conn.ssa",       "Conn USSD                "},
+	[BCFG_CTR_CON_OTHER]     = { "conn.other",     "Conn Other               "},
+};
+
+static const struct rate_ctr_group_desc bsc_cfg_ctrg_desc = {
+	.group_name_prefix = "nat.bsc",
+	.group_description = "NAT BSC Statistics",
+	.num_ctr = ARRAY_SIZE(bsc_cfg_ctr_description),
+	.ctr_desc = bsc_cfg_ctr_description,
+};
+
+static const struct rate_ctr_desc acc_list_ctr_description[] = {
+	[ACC_LIST_BSC_FILTER]	= { "access-list.bsc-filter", "Rejected by rule for BSC"},
+	[ACC_LIST_NAT_FILTER]	= { "access-list.nat-filter", "Rejected by rule for NAT"},
+};
+
+static const struct rate_ctr_group_desc bsc_cfg_acc_list_desc = {
+	.group_name_prefix = "nat.filter",
+	.group_description = "NAT Access-List Statistics",
+	.num_ctr = ARRAY_SIZE(acc_list_ctr_description),
+	.ctr_desc = acc_list_ctr_description,
+};
+
+struct bsc_nat *bsc_nat_alloc(void)
+{
+	struct bsc_nat *nat = talloc_zero(tall_bsc_ctx, struct bsc_nat);
+	if (!nat)
+		return NULL;
+
+	INIT_LLIST_HEAD(&nat->sccp_connections);
+	INIT_LLIST_HEAD(&nat->bsc_connections);
+	INIT_LLIST_HEAD(&nat->bsc_configs);
+	INIT_LLIST_HEAD(&nat->access_lists);
+
+	nat->stats.sccp.conn = counter_alloc("nat.sccp.conn");
+	nat->stats.sccp.calls = counter_alloc("nat.sccp.calls");
+	nat->stats.bsc.reconn = counter_alloc("nat.bsc.conn");
+	nat->stats.bsc.auth_fail = counter_alloc("nat.bsc.auth_fail");
+	nat->stats.msc.reconn = counter_alloc("nat.msc.conn");
+	nat->stats.ussd.reconn = counter_alloc("nat.ussd.conn");
+	nat->msc_ip = talloc_strdup(nat, "127.0.0.1");
+	nat->msc_port = 5000;
+	nat->auth_timeout = 2;
+	nat->ping_timeout = 20;
+	nat->pong_timeout = 5;
+	return nat;
+}
+
+void bsc_nat_set_msc_ip(struct bsc_nat *nat, const char *ip)
+{
+	bsc_replace_string(nat, &nat->msc_ip, ip);
+}
+
+struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat)
+{
+	struct bsc_connection *con = talloc_zero(nat, struct bsc_connection);
+	if (!con)
+		return NULL;
+
+	con->nat = nat;
+	write_queue_init(&con->write_queue, 100);
+	return con;
+}
+
+struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token)
+{
+	struct bsc_config *conf = talloc_zero(nat, struct bsc_config);
+	if (!conf)
+		return NULL;
+
+	conf->token = talloc_strdup(conf, token);
+	conf->nr = nat->num_bsc;
+	conf->nat = nat;
+	conf->max_endpoints = 32;
+
+	INIT_LLIST_HEAD(&conf->lac_list);
+
+	llist_add_tail(&conf->entry, &nat->bsc_configs);
+	++nat->num_bsc;
+
+	conf->stats.ctrg = rate_ctr_group_alloc(conf, &bsc_cfg_ctrg_desc, conf->nr);
+	if (!conf->stats.ctrg) {
+		talloc_free(conf);
+		return NULL;
+	}
+
+	return conf;
+}
+
+void bsc_config_free(struct bsc_config *cfg)
+{
+	rate_ctr_group_free(cfg->stats.ctrg);
+}
+
+void bsc_config_add_lac(struct bsc_config *cfg, int _lac)
+{
+	struct bsc_lac_entry *lac;
+
+	llist_for_each_entry(lac, &cfg->lac_list, entry)
+		if (lac->lac == _lac)
+			return;
+
+	lac = talloc_zero(cfg, struct bsc_lac_entry);
+	if (!lac) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+		return;
+	}
+
+	lac->lac = _lac;
+	llist_add_tail(&lac->entry, &cfg->lac_list);
+}
+
+void bsc_config_del_lac(struct bsc_config *cfg, int _lac)
+{
+	struct bsc_lac_entry *lac;
+
+	llist_for_each_entry(lac, &cfg->lac_list, entry)
+		if (lac->lac == _lac) {
+			llist_del(&lac->entry);
+			talloc_free(lac);
+			return;
+		}
+}
+
+int bsc_config_handles_lac(struct bsc_config *cfg, int lac_nr)
+{
+	struct bsc_lac_entry *entry;
+
+	llist_for_each_entry(entry, &cfg->lac_list, entry)
+		if (entry->lac == lac_nr)
+			return 1;
+
+	return 0;
+}
+
+void sccp_connection_destroy(struct sccp_connections *conn)
+{
+	LOGP(DNAT, LOGL_DEBUG, "Destroy 0x%x <-> 0x%x mapping for con %p\n",
+	     sccp_src_ref_to_int(&conn->real_ref),
+	     sccp_src_ref_to_int(&conn->patched_ref), conn->bsc);
+	bsc_mgcp_dlcx(conn);
+	llist_del(&conn->list_entry);
+	talloc_free(conn);
+}
+
+struct bsc_connection *bsc_nat_find_bsc(struct bsc_nat *nat, struct msgb *msg, int *lac_out)
+{
+	struct bsc_connection *bsc;
+	int data_length;
+	const uint8_t *data;
+	struct tlv_parsed tp;
+	int i = 0;
+
+	*lac_out = -1;
+
+	if (!msg->l3h || msgb_l3len(msg) < 3) {
+		LOGP(DNAT, LOGL_ERROR, "Paging message is too short.\n");
+		return NULL;
+	}
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+		LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n");
+		return NULL;
+	}
+
+	data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+	data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+
+	/* No need to try a different BSS */
+	if (data[0] == CELL_IDENT_BSS) {
+		return NULL;
+	} else if (data[0] != CELL_IDENT_LAC) {
+		LOGP(DNAT, LOGL_ERROR, "Unhandled cell ident discrminator: %d\n", data[0]);
+		return NULL;
+	}
+
+	/* Currently we only handle one BSC */
+	for (i = 1; i < data_length - 1; i += 2) {
+		unsigned int _lac = ntohs(*(unsigned int *) &data[i]);
+		*lac_out = _lac;
+		llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+			if (!bsc->cfg)
+				continue;
+			if (!bsc->authenticated)
+				continue;
+			if (!bsc_config_handles_lac(bsc->cfg, _lac))
+				continue;
+
+			return bsc;
+		}
+	}
+
+	return NULL;
+}
+
+int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length)
+{
+	struct msgb *msg;
+
+	if (length > 4096 - 128) {
+		LOGP(DINP, LOGL_ERROR, "Can not send message of that size.\n");
+		return -1;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "to-bsc");
+	if (!msg) {
+		LOGP(DINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n");
+		return -1;
+	}
+
+	/* copy the data */
+	msg->l3h = msgb_put(msg, length);
+	memcpy(msg->l3h, data, length);
+
+        return bsc_write(bsc, msg, IPAC_PROTO_MGCP_OLD);
+}
+
+int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int proto)
+{
+	return bsc_do_write(&bsc->write_queue, msg, proto);
+}
+
+int bsc_do_write(struct write_queue *queue, struct msgb *msg, int proto)
+{
+	/* prepend the header */
+	ipaccess_prepend_header(msg, proto);
+	return bsc_write_msg(queue, msg);
+}
+
+int bsc_write_msg(struct write_queue *queue, struct msgb *msg)
+{
+	if (write_queue_enqueue(queue, msg) != 0) {
+		LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	return 0;
+}
+
+int bsc_nat_lst_check_allow(struct bsc_nat_acc_lst *lst, const char *mi_string)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (!entry->imsi_allow)
+			continue;
+		if (regexec(&entry->imsi_allow_re, mi_string, 0, NULL, 0) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+static int lst_check_deny(struct bsc_nat_acc_lst *lst, const char *mi_string)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (!entry->imsi_deny)
+			continue;
+		if (regexec(&entry->imsi_deny_re, mi_string, 0, NULL, 0) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+/* apply white/black list */
+static int auth_imsi(struct bsc_connection *bsc, const char *mi_string)
+{
+	/*
+	 * Now apply blacklist/whitelist of the BSC and the NAT.
+	 * 1.) Allow directly if the IMSI is allowed at the BSC
+	 * 2.) Reject if the IMSI is not allowed at the BSC
+	 * 3.) Reject if the IMSI not allowed at the global level.
+	 * 4.) Allow directly if the IMSI is allowed at the global level
+	 */
+	struct bsc_nat_acc_lst *nat_lst = NULL;
+	struct bsc_nat_acc_lst *bsc_lst = NULL;
+
+	bsc_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->cfg->acc_lst_name);
+	nat_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->nat->acc_lst_name);
+
+
+	if (bsc_lst) {
+		/* 1. BSC allow */
+		if (bsc_nat_lst_check_allow(bsc_lst, mi_string) == 0)
+			return 1;
+
+		/* 2. BSC deny */
+		if (lst_check_deny(bsc_lst, mi_string) == 0) {
+			LOGP(DNAT, LOGL_ERROR,
+			     "Filtering %s by imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr);
+			rate_ctr_inc(&bsc_lst->stats->ctr[ACC_LIST_BSC_FILTER]);
+			return -2;
+		}
+
+	}
+
+	/* 3. NAT deny */
+	if (nat_lst) {
+		if (lst_check_deny(nat_lst, mi_string) == 0) {
+			LOGP(DNAT, LOGL_ERROR,
+			     "Filtering %s by nat imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr);
+			rate_ctr_inc(&nat_lst->stats->ctr[ACC_LIST_NAT_FILTER]);
+			return -3;
+		}
+	}
+
+	return 1;
+}
+
+static int _cr_check_loc_upd(struct bsc_connection *bsc,
+			     uint8_t *data, unsigned int length,
+			     char **imsi)
+{
+	uint8_t mi_type;
+	struct gsm48_loc_upd_req *lu;
+	char mi_string[GSM48_MI_SIZE];
+
+	if (length < sizeof(*lu)) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "LU does not fit. Length is %d \n", length);
+		return -1;
+	}
+
+	lu = (struct gsm48_loc_upd_req *) data;
+	mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+	/*
+	 * We can only deal with the IMSI. This will fail for a phone that
+	 * will send the TMSI of a previous network to us.
+	 */
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+	*imsi = talloc_strdup(bsc, mi_string);
+	return auth_imsi(bsc, mi_string);
+}
+
+static int _cr_check_cm_serv_req(struct bsc_connection *bsc,
+				 uint8_t *data, unsigned int length,
+				 int *con_type, char **imsi)
+{
+	static const uint32_t classmark_offset =
+				offsetof(struct gsm48_service_request, classmark);
+
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+	int rc;
+	struct gsm48_service_request *req;
+
+	/* unfortunately in Phase1 the classmark2 length is variable */
+
+	if (length < sizeof(*req)) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "CM Serv Req does not fit. Length is %d\n", length);
+		return -1;
+	}
+
+	req = (struct gsm48_service_request *) data;
+	if (req->cm_service_type == 0x8)
+		*con_type = NAT_CON_TYPE_SSA;
+	rc = gsm48_extract_mi((uint8_t *) &req->classmark,
+			      length - classmark_offset, mi_string, &mi_type);
+	if (rc < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to parse the classmark2/mi. error: %d\n", rc);
+		return -1;
+	}
+
+	/* we have to let the TMSI or such pass */
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	*imsi = talloc_strdup(bsc, mi_string);
+	return auth_imsi(bsc, mi_string);
+}
+
+static int _cr_check_pag_resp(struct bsc_connection *bsc,
+			      uint8_t *data, unsigned int length,
+			      char **imsi)
+{
+	struct gsm48_pag_resp *resp;
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+
+	if (length < sizeof(*resp)) {
+		LOGP(DNAT, LOGL_ERROR, "PAG RESP does not fit. Length was %d.\n", length);
+		return -1;
+	}
+
+	resp = (struct gsm48_pag_resp *) data;
+	if (gsm48_paging_extract_mi(resp, length, mi_string, &mi_type) < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to extract the MI.\n");
+		return -1;
+	}
+
+	/* we need to let it pass for now */
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	*imsi = talloc_strdup(bsc, mi_string);
+	return auth_imsi(bsc, mi_string);
+}
+
+static int _dt_check_id_resp(struct bsc_connection *bsc,
+			     uint8_t *data, unsigned int length,
+			     struct sccp_connections *con)
+{
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+	int ret;
+
+	if (length < 2) {
+		LOGP(DNAT, LOGL_ERROR, "mi does not fit.\n");
+		return -1;
+	}
+
+	if (data[0] < length - 1) {
+		LOGP(DNAT, LOGL_ERROR, "mi length too big.\n");
+		return -2;
+	}
+
+	mi_type = data[1] & GSM_MI_TYPE_MASK;
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &data[1], data[0]);
+
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	ret = auth_imsi(bsc, mi_string);
+	con->imsi_checked = 1;
+	con->imsi = talloc_strdup(con, mi_string);
+	return ret;
+}
+
+/* Filter out CR data... */
+int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg,
+			   struct bsc_nat_parsed *parsed, int *con_type,
+			   char **imsi)
+{
+	struct tlv_parsed tp;
+	struct gsm48_hdr *hdr48;
+	int hdr48_len;
+	int len;
+	uint8_t msg_type;
+
+	*con_type = NAT_CON_TYPE_NONE;
+	*imsi = NULL;
+
+	if (parsed->gsm_type != BSS_MAP_MSG_COMPLETE_LAYER_3) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "Rejecting CR message due wrong GSM Type %d\n", parsed->gsm_type);
+		return -1;
+	}
+
+	/* the parsed has had some basic l3 length check */
+	len = msg->l3h[1];
+	if (msgb_l3len(msg) - 3 < len) {
+		LOGP(DNAT, LOGL_ERROR,
+		     "The CR Data has not enough space...\n");
+		return -1;
+	}
+
+	msg->l4h = &msg->l3h[3];
+	len -= 1;
+
+	tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h, len, 0, 0);
+
+	if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) {
+		LOGP(DNAT, LOGL_ERROR, "CR Data does not contain layer3 information.\n");
+		return -1;
+	}
+
+	hdr48_len = TLVP_LEN(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+
+	if (hdr48_len < sizeof(*hdr48)) {
+		LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n");
+		return -1;
+	}
+
+	hdr48 = (struct gsm48_hdr *) TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+
+	msg_type = hdr48->msg_type & 0xbf;
+	if (hdr48->proto_discr == GSM48_PDISC_MM &&
+	    msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) {
+		*con_type = NAT_CON_TYPE_LU;
+		return _cr_check_loc_upd(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48), imsi);
+	} else if (hdr48->proto_discr == GSM48_PDISC_MM &&
+		  msg_type == GSM48_MT_MM_CM_SERV_REQ) {
+		*con_type = NAT_CON_TYPE_CM_SERV_REQ;
+		return _cr_check_cm_serv_req(bsc, &hdr48->data[0],
+					     hdr48_len - sizeof(*hdr48),
+					     con_type, imsi);
+	} else if (hdr48->proto_discr == GSM48_PDISC_RR &&
+		   msg_type == GSM48_MT_RR_PAG_RESP) {
+		*con_type = NAT_CON_TYPE_PAG_RESP;
+		return _cr_check_pag_resp(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48), imsi);
+	} else {
+		/* We only want to filter the above, let other things pass */
+		*con_type = NAT_CON_TYPE_OTHER;
+		return 0;
+	}
+}
+
+struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed,
+				  struct msgb *msg, uint32_t *len)
+{
+	/* gsm_type is actually the size of the dtap */
+	*len = parsed->gsm_type;
+	if (*len < msgb_l3len(msg) - 3) {
+		LOGP(DNAT, LOGL_ERROR, "Not enough space for DTAP.\n");
+		return NULL;
+	}
+
+	if (*len < sizeof(struct gsm48_hdr)) {
+		LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n");
+		return NULL;
+	}
+
+	msg->l4h = &msg->l3h[3];
+	return (struct gsm48_hdr *) msg->l4h;
+}
+
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+		      struct sccp_connections *con, struct bsc_nat_parsed *parsed)
+{
+	uint32_t len;
+	uint8_t msg_type;
+	struct gsm48_hdr *hdr48;
+
+	if (con->imsi_checked)
+		return 0;
+
+	/* only care about DTAP messages */
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return 0;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return -1;
+
+	msg_type = hdr48->msg_type & 0xbf;
+	if (hdr48->proto_discr == GSM48_PDISC_MM &&
+	    msg_type == GSM48_MT_MM_ID_RESP) {
+		return _dt_check_id_resp(bsc, &hdr48->data[0], len - sizeof(*hdr48), con);
+	} else {
+		return 0;
+	}
+}
+
+void bsc_parse_reg(void *ctx, regex_t *reg, char **imsi, int argc, const char **argv)
+{
+	if (*imsi) {
+		talloc_free(*imsi);
+		*imsi = NULL;
+	}
+	regfree(reg);
+
+	if (argc > 0) {
+		*imsi = talloc_strdup(ctx, argv[0]);
+		regcomp(reg, argv[0], 0);
+	}
+}
+
+static const char *con_types [] = {
+	[NAT_CON_TYPE_NONE] = "n/a",
+	[NAT_CON_TYPE_LU] = "Location Update",
+	[NAT_CON_TYPE_CM_SERV_REQ] = "CM Serv Req",
+	[NAT_CON_TYPE_PAG_RESP] = "Paging Response",
+	[NAT_CON_TYPE_SSA] = "Supplementar Service Activation",
+	[NAT_CON_TYPE_LOCAL_REJECT] = "Local Reject",
+	[NAT_CON_TYPE_OTHER] = "Other",
+};
+
+const char *bsc_con_type_to_string(int type)
+{
+	return con_types[type];
+}
+
+struct bsc_nat_acc_lst *bsc_nat_acc_lst_find(struct bsc_nat *nat, const char *name)
+{
+	struct bsc_nat_acc_lst *lst;
+
+	if (!name)
+		return NULL;
+
+	llist_for_each_entry(lst, &nat->access_lists, list)
+		if (strcmp(lst->name, name) == 0)
+			return lst;
+
+	return NULL;
+}
+
+struct bsc_nat_acc_lst *bsc_nat_acc_lst_get(struct bsc_nat *nat, const char *name)
+{
+	struct bsc_nat_acc_lst *lst;
+
+	lst = bsc_nat_acc_lst_find(nat, name);
+	if (lst)
+		return lst;
+
+	lst = talloc_zero(nat, struct bsc_nat_acc_lst);
+	if (!lst) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate access list");
+		return NULL;
+	}
+
+	/* TODO: get the index right */
+	lst->stats = rate_ctr_group_alloc(lst, &bsc_cfg_acc_list_desc, 0);
+	if (!lst->stats) {
+		talloc_free(lst);
+		return NULL;
+	}
+
+	INIT_LLIST_HEAD(&lst->fltr_list);
+	lst->name = talloc_strdup(lst, name);
+	llist_add_tail(&lst->list, &nat->access_lists);
+	return lst;
+}
+
+void bsc_nat_acc_lst_delete(struct bsc_nat_acc_lst *lst)
+{
+	llist_del(&lst->list);
+	rate_ctr_group_free(lst->stats);
+	talloc_free(lst);
+}
+
+struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *lst)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	entry = talloc_zero(lst, struct bsc_nat_acc_lst_entry);
+	if (!entry)
+		return NULL;
+
+	llist_add_tail(&entry->list, &lst->fltr_list);
+	return entry;
+}
+
+int bsc_nat_msc_is_connected(struct bsc_nat *nat)
+{
+	return nat->msc_con->is_connected;
+}
+
+static const int con_to_ctr[] = {
+	[NAT_CON_TYPE_NONE]		= -1,
+	[NAT_CON_TYPE_LU]		= BCFG_CTR_CON_TYPE_LU,
+	[NAT_CON_TYPE_CM_SERV_REQ]	= BCFG_CTR_CON_CMSERV_RQ,
+	[NAT_CON_TYPE_PAG_RESP]		= BCFG_CTR_CON_PAG_RESP,
+	[NAT_CON_TYPE_SSA]		= BCFG_CTR_CON_SSA,
+	[NAT_CON_TYPE_LOCAL_REJECT]	= -1,
+	[NAT_CON_TYPE_OTHER]		= BCFG_CTR_CON_OTHER,
+};
+
+int bsc_conn_type_to_ctr(struct sccp_connections *conn)
+{
+	return con_to_ctr[conn->con_type];
+}
+
+int bsc_write_cb(struct bsc_fd *bfd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(bfd->fd, msg->data, msg->len);
+	if (rc != msg->len)
+		LOGP(DNAT, LOGL_ERROR, "Failed to write message to the BSC.\n");
+
+	return rc;
+}
+
+/**
+ * Rewrite non global numbers... according to rules based on the IMSI
+ */
+struct msgb *bsc_nat_rewrite_setup(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *parsed, const char *imsi)
+{
+	struct tlv_parsed tp;
+	struct gsm48_hdr *hdr48;
+	uint32_t len;
+	uint8_t msg_type;
+	unsigned int payload_len;
+	struct gsm_mncc_number called;
+	struct msg_entry *entry;
+	char *new_number = NULL;
+	struct msgb *out, *sccp;
+	uint8_t *outptr;
+	const uint8_t *msgptr;
+	int sec_len;
+
+	if (!imsi || strlen(imsi) < 5)
+		return msg;
+
+	if (!nat->num_rewr)
+		return msg;
+
+	/* only care about DTAP messages */
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return msg;
+	if (!parsed->dest_local_ref)
+		return msg;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return msg;
+
+	msg_type = hdr48->msg_type & 0xbf;
+	if (hdr48->proto_discr != GSM48_PDISC_CC ||
+	    msg_type != GSM48_MT_CC_SETUP)
+		return msg;
+
+	/* decode and rewrite the message */
+	payload_len = len - sizeof(*hdr48);
+	tlv_parse(&tp, &gsm48_att_tlvdef, hdr48->data, payload_len, 0, 0);
+
+	/* no number, well let us ignore it */
+	if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD))
+		return msg;
+
+	memset(&called, 0, sizeof(called));
+	gsm48_decode_called(&called,
+			    TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+	/* check if it looks international and stop */
+	if (called.plan != 1)
+		return msg;
+	if (called.type == 1)
+		return msg;
+	if (strncmp(called.number, "00", 2) == 0)
+		return msg;
+
+	/* need to find a replacement and then fix it */
+	llist_for_each_entry(entry, &nat->num_rewr->entry, list) {
+		regex_t reg;
+		regmatch_t matches[2];
+
+		if (entry->mcc[0] != '*' && strncmp(entry->mcc, imsi, 3) != 0)
+			continue;
+		if (entry->mnc[0] != '*' && strncmp(entry->mnc, imsi + 3, 2) != 0)
+			continue;
+
+		if (entry->text[0] == '+') {
+			LOGP(DNAT, LOGL_ERROR,
+				"Plus is not allowed in the number");
+			continue;
+		}
+
+		/* We have an entry for the IMSI. Need to match now */
+		if (regcomp(&reg, entry->option, REG_EXTENDED) != 0) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Regexp '%s' is not valid.\n", entry->option);
+			continue;
+		}
+
+		/* this regexp matches... */
+		if (regexec(&reg, called.number, 2, matches, 0) == 0 &&
+		    matches[1].rm_eo != -1)
+			new_number = talloc_asprintf(msg, "%s%s",
+					entry->text,
+					&called.number[matches[1].rm_so]);
+		regfree(&reg);
+
+		if (new_number)
+			break;
+	}
+
+	if (!new_number) {
+		LOGP(DNAT, LOGL_DEBUG, "No IMSI match found, returning message.\n");
+		return msg;
+	}
+
+	if (strlen(new_number) > sizeof(called.number)) {
+		LOGP(DNAT, LOGL_ERROR, "Number is too long for structure.\n");
+		talloc_free(new_number);
+		return msg;
+	}
+
+	/*
+	 * Need to create a new message now based on the old onew
+	 * with a new number. We can sadly not patch this in place
+	 * so we will need to regenerate it.
+	 */
+
+	out = msgb_alloc_headroom(4096, 128, "changed-setup");
+	if (!out) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+		talloc_free(new_number);
+		return msg;
+	}
+
+	/* copy the header */
+	outptr = msgb_put(out, sizeof(*hdr48));
+	memcpy(outptr, hdr48, sizeof(*hdr48));
+
+	/* copy everything up to the number */
+	sec_len = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 2 - &hdr48->data[0];
+	outptr = msgb_put(out, sec_len);
+	memcpy(outptr, &hdr48->data[0], sec_len);
+
+	/* create the new number */
+	if (strncmp(new_number, "00", 2) == 0) {
+		called.type = 1;
+		strncpy(called.number, new_number + 2, sizeof(called.number));
+	} else {
+		strncpy(called.number, new_number, sizeof(called.number));
+	}
+	gsm48_encode_called(out, &called);
+
+	/* copy thre rest */
+	msgptr = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) +
+		 TLVP_LEN(&tp, GSM48_IE_CALLED_BCD);
+	sec_len = payload_len - (msgptr - &hdr48->data[0]);
+	outptr = msgb_put(out, sec_len);
+	memcpy(outptr, msgptr, sec_len);
+
+	/* wrap with DTAP, SCCP, then IPA. TODO: Stop copying */
+	gsm0808_prepend_dtap_header(out, 0);
+	sccp = sccp_create_dt1(parsed->dest_local_ref, out->data, out->len);
+	if (!sccp) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+		talloc_free(new_number);
+		talloc_free(out);
+		return msg;
+	}
+
+	ipaccess_prepend_header(sccp, IPAC_PROTO_SCCP);
+
+	/* give up memory, we are done */
+	talloc_free(new_number);
+	/* the parsed hangs off from msg but it needs to survive */
+	talloc_steal(sccp, parsed);
+	msgb_free(msg);
+	msgb_free(out);
+	out = NULL;
+	return sccp;
+}
+
diff --git a/src/osmo-bsc_nat/bsc_nat_vty.c b/src/osmo-bsc_nat/bsc_nat_vty.c
new file mode 100644
index 0000000..786db2d
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_vty.c
@@ -0,0 +1,788 @@
+/* OpenBSC NAT interface to quagga VTY */
+/* (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/vty.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/vty.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/rate_ctr.h>
+#include <osmocore/utils.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <stdlib.h>
+
+static struct bsc_nat *_nat;
+
+static struct cmd_node nat_node = {
+	NAT_NODE,
+	"%s(nat)#",
+	1,
+};
+
+static struct cmd_node bsc_node = {
+	NAT_BSC_NODE,
+	"%s(bsc)#",
+	1,
+};
+
+static void write_acc_lst(struct vty *vty, struct bsc_nat_acc_lst *lst)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (entry->imsi_allow)
+			vty_out(vty, " access-list %s imsi-allow %s%s",
+				lst->name, entry->imsi_allow, VTY_NEWLINE);
+		if (entry->imsi_deny)
+			vty_out(vty, " access-list %s imsi-deny %s%s",
+				lst->name, entry->imsi_deny, VTY_NEWLINE);
+	}
+}
+
+static int config_write_nat(struct vty *vty)
+{
+	struct bsc_nat_acc_lst *lst;
+
+	vty_out(vty, "nat%s", VTY_NEWLINE);
+	vty_out(vty, " msc ip %s%s", _nat->msc_ip, VTY_NEWLINE);
+	vty_out(vty, " msc port %d%s", _nat->msc_port, VTY_NEWLINE);
+	vty_out(vty, " timeout auth %d%s", _nat->auth_timeout, VTY_NEWLINE);
+	vty_out(vty, " timeout ping %d%s", _nat->ping_timeout, VTY_NEWLINE);
+	vty_out(vty, " timeout pong %d%s", _nat->pong_timeout, VTY_NEWLINE);
+	if (_nat->token)
+		vty_out(vty, " token %s%s", _nat->token, VTY_NEWLINE);
+	vty_out(vty, " ip-dscp %d%s", _nat->bsc_ip_dscp, VTY_NEWLINE);
+	if (_nat->acc_lst_name)
+		vty_out(vty, " access-list-name %s%s", _nat->acc_lst_name, VTY_NEWLINE);
+	if (_nat->ussd_lst_name)
+		vty_out(vty, " ussd-list-name %s%s", _nat->ussd_lst_name, VTY_NEWLINE);
+	if (_nat->ussd_query)
+		vty_out(vty, " ussd-query %s%s", _nat->ussd_query, VTY_NEWLINE);
+	if (_nat->ussd_token)
+		vty_out(vty, " ussd-token %s%s", _nat->ussd_token, VTY_NEWLINE);
+	if (_nat->ussd_local)
+		vty_out(vty, " ussd-local-ip %s%s", _nat->ussd_local, VTY_NEWLINE);
+
+	if (_nat->num_rewr_name)
+		vty_out(vty, " number-rewrite %s%s", _nat->num_rewr_name, VTY_NEWLINE);
+
+	llist_for_each_entry(lst, &_nat->access_lists, list) {
+		write_acc_lst(vty, lst);
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void dump_lac(struct vty *vty, struct bsc_config *cfg)
+{
+	struct bsc_lac_entry *lac;
+	llist_for_each_entry(lac, &cfg->lac_list, entry)
+		vty_out(vty, "  location_area_code %u%s", lac->lac, VTY_NEWLINE);
+}
+
+static void config_write_bsc_single(struct vty *vty, struct bsc_config *bsc)
+{
+	vty_out(vty, " bsc %u%s", bsc->nr, VTY_NEWLINE);
+	vty_out(vty, "  token %s%s", bsc->token, VTY_NEWLINE);
+	dump_lac(vty, bsc);
+	vty_out(vty, "  paging forbidden %d%s", bsc->forbid_paging, VTY_NEWLINE);
+	if (bsc->description)
+		vty_out(vty, "  description %s%s", bsc->description, VTY_NEWLINE);
+	if (bsc->acc_lst_name)
+		vty_out(vty, "  access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
+	vty_out(vty, "  max-endpoints %d%s", bsc->max_endpoints, VTY_NEWLINE);
+}
+
+static int config_write_bsc(struct vty *vty)
+{
+	struct bsc_config *bsc;
+
+	llist_for_each_entry(bsc, &_nat->bsc_configs, entry)
+		config_write_bsc_single(vty, bsc);
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(show_sccp, show_sccp_cmd, "show sccp connections",
+      SHOW_STR "Display information about current SCCP connections")
+{
+	struct sccp_connections *con;
+	vty_out(vty, "Listing all open SCCP connections%s", VTY_NEWLINE);
+
+	llist_for_each_entry(con, &_nat->sccp_connections, list_entry) {
+		vty_out(vty, "For BSC Nr: %d BSC ref: 0x%x; MUX ref: 0x%x; Network has ref: %d ref: 0x%x MSC/BSC mux: 0x%x/0x%x type: %s%s",
+			con->bsc->cfg ? con->bsc->cfg->nr : -1,
+			sccp_src_ref_to_int(&con->real_ref),
+			sccp_src_ref_to_int(&con->patched_ref),
+			con->has_remote_ref,
+			sccp_src_ref_to_int(&con->remote_ref),
+			con->msc_endp, con->bsc_endp,
+			bsc_con_type_to_string(con->con_type),
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc, show_bsc_cmd, "show bsc connections",
+      SHOW_STR "Display information about current BSCs")
+{
+	struct bsc_connection *con;
+	struct sockaddr_in sock;
+	socklen_t len = sizeof(sock);
+
+	llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+		getpeername(con->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+		vty_out(vty, "BSC nr: %d auth: %d fd: %d peername: %s%s",
+			con->cfg ? con->cfg->nr : -1,
+			con->authenticated, con->write_queue.bfd.fd,
+			inet_ntoa(sock.sin_addr), VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc_mgcp, show_bsc_mgcp_cmd, "show bsc mgcp NR",
+      SHOW_STR "Display the MGCP status for a given BSC")
+{
+	struct bsc_connection *con;
+	int nr = atoi(argv[0]);
+	int i, j, endp;
+
+	llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+		int max;
+		if (!con->cfg)
+			continue;
+		if (con->cfg->nr != nr)
+			continue;
+
+		/* this bsc has no audio endpoints yet */
+		if (!con->_endpoint_status)
+			continue;
+
+		vty_out(vty, "MGCP Status for %d%s", con->cfg->nr, VTY_NEWLINE);
+		max = bsc_mgcp_nr_multiplexes(con->max_endpoints);
+		for (i = 0; i < max; ++i) {
+			for (j = 0; j < 32; ++j) {
+				endp = mgcp_timeslot_to_endpoint(i, j);
+				vty_out(vty, " Endpoint 0x%x %s%s", endp,
+					con->_endpoint_status[endp] == 0 
+						? "free" : "allocated",
+				VTY_NEWLINE);
+			}
+		}
+		break;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc_cfg, show_bsc_cfg_cmd, "show bsc config",
+      SHOW_STR "Display information about known BSC configs")
+{
+	struct bsc_config *conf;
+	llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+		vty_out(vty, "BSC token: '%s' nr: %u%s",
+			conf->token, conf->nr, VTY_NEWLINE);
+		if (conf->acc_lst_name)
+			vty_out(vty, " access-list: %s%s",
+				conf->acc_lst_name, VTY_NEWLINE);
+		vty_out(vty, " paging forbidden: %d%s",
+			conf->forbid_paging, VTY_NEWLINE);
+		if (conf->description)
+			vty_out(vty, " description: %s%s", conf->description, VTY_NEWLINE);
+		else
+			vty_out(vty, " No description.%s", VTY_NEWLINE);
+
+	}
+
+	return CMD_SUCCESS;
+}
+
+static void dump_stat_total(struct vty *vty, struct bsc_nat *nat)
+{
+	vty_out(vty, "NAT statistics%s", VTY_NEWLINE);
+	vty_out(vty, " SCCP Connections %lu total, %lu calls%s",
+		counter_get(nat->stats.sccp.conn),
+		counter_get(nat->stats.sccp.calls), VTY_NEWLINE);
+	vty_out(vty, " MSC Connections %lu%s",
+		counter_get(nat->stats.msc.reconn), VTY_NEWLINE);
+	vty_out(vty, " MSC Connected: %d%s",
+		nat->msc_con->is_connected, VTY_NEWLINE);
+	vty_out(vty, " BSC Connections %lu total, %lu auth failed.%s",
+		counter_get(nat->stats.bsc.reconn),
+		counter_get(nat->stats.bsc.auth_fail), VTY_NEWLINE);
+}
+
+static void dump_stat_bsc(struct vty *vty, struct bsc_config *conf)
+{
+	int connected = 0;
+	struct bsc_connection *con;
+
+	vty_out(vty, " BSC nr: %d%s",
+		conf->nr, VTY_NEWLINE);
+	vty_out_rate_ctr_group(vty, " ", conf->stats.ctrg);
+
+	llist_for_each_entry(con, &conf->nat->bsc_connections, list_entry) {
+		if (con->cfg != conf)
+			continue;
+		connected = 1;
+		break;
+	}
+
+	vty_out(vty, "  Connected: %d%s", connected, VTY_NEWLINE);
+}
+
+DEFUN(show_stats,
+      show_stats_cmd,
+      "show statistics [NR]",
+	SHOW_STR "Display network statistics")
+{
+	struct bsc_config *conf;
+
+	int nr = -1;
+
+	if (argc == 1)
+		nr = atoi(argv[0]);
+
+	dump_stat_total(vty, _nat);
+	llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+		if (argc == 1 && nr != conf->nr)
+			continue;
+		dump_stat_bsc(vty, conf);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_stats_lac,
+      show_stats_lac_cmd,
+      "show statistics-by-lac <0-65535>",
+      SHOW_STR "Display network statistics by lac\n"
+      "The lac of the BSC\n")
+{
+	int lac;
+	struct bsc_config *conf;
+
+	lac = atoi(argv[0]);
+
+	dump_stat_total(vty, _nat);
+	llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+		if (!bsc_config_handles_lac(conf, lac))
+			continue;
+		dump_stat_bsc(vty, conf);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_msc,
+      show_msc_cmd,
+      "show msc connection",
+      SHOW_STR "Show the status of the MSC connection.")
+{
+	if (!_nat->msc_con) {
+		vty_out(vty, "The MSC is not yet configured.\n");
+		return CMD_WARNING;
+	}
+
+	vty_out(vty, "MSC on %s:%d is connected: %d%s\n",
+		_nat->msc_con->ip, _nat->msc_con->port,
+		_nat->msc_con->is_connected, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(close_bsc,
+      close_bsc_cmd,
+      "close bsc connection BSC_NR",
+      "Close the connection with the BSC identified by the config number.")
+{
+	struct bsc_connection *bsc;
+	int bsc_nr = atoi(argv[0]);
+
+	llist_for_each_entry(bsc, &_nat->bsc_connections, list_entry) {
+		if (!bsc->cfg || bsc->cfg->nr != bsc_nr)
+			continue;
+		bsc_close_connection(bsc);
+		break;
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat, cfg_nat_cmd, "nat", "Configute the NAT")
+{
+	vty->index = _nat;
+	vty->node = NAT_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_msc_ip,
+      cfg_nat_msc_ip_cmd,
+      "msc ip A.B.C.D",
+      "Set the IP address of the MSC.")
+{
+	bsc_nat_set_msc_ip(_nat, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_msc_port,
+      cfg_nat_msc_port_cmd,
+      "msc port <1-65500>",
+      "Set the port of the MSC.")
+{
+	_nat->msc_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_auth_time,
+      cfg_nat_auth_time_cmd,
+      "timeout auth <1-256>",
+      "The time to wait for an auth response.")
+{
+	_nat->auth_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ping_time,
+      cfg_nat_ping_time_cmd,
+      "timeout ping NR",
+      "Send a ping every NR seconds. Negative to disable.")
+{
+	_nat->ping_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_pong_time,
+      cfg_nat_pong_time_cmd,
+      "timeout pong NR",
+      "Wait NR seconds for the PONG response. Should be smaller than ping.")
+{
+	_nat->pong_timeout = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_token, cfg_nat_token_cmd,
+      "token TOKEN",
+      "Set a token for the NAT")
+{
+	bsc_replace_string(_nat, &_nat->token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_dscp_cmd,
+      "ip-dscp <0-255>",
+      "Set the IP DSCP for the BSCs to use\n" "Set the IP_TOS attribute")
+{
+	_nat->bsc_ip_dscp = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_tos_cmd,
+      "ip-tos <0-255>",
+      "Use ip-dscp in the future.\n" "Set the DSCP\n")
+
+
+DEFUN(cfg_nat_acc_lst_name,
+      cfg_nat_acc_lst_name_cmd,
+      "access-list-name NAME",
+      "Set the name of the access list to use.\n"
+      "The name of the to be used access list.")
+{
+	bsc_replace_string(_nat, &_nat->acc_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_number_rewrite,
+      cfg_nat_number_rewrite_cmd,
+      "number-rewrite FILENAME",
+      "Set the file with rewriting rules.\n" "Filename")
+{
+	bsc_replace_string(_nat, &_nat->num_rewr_name, argv[0]);
+	if (_nat->num_rewr_name) {
+		if (_nat->num_rewr)
+			talloc_free(_nat->num_rewr);
+		_nat->num_rewr = msg_entry_parse(_nat, _nat->num_rewr_name);
+		return _nat->num_rewr == NULL ? CMD_WARNING : CMD_SUCCESS;
+	} else {
+		if (_nat->num_rewr)
+			talloc_free(_nat->num_rewr);
+		_nat->num_rewr = NULL;
+		return CMD_SUCCESS;
+	}
+}
+
+DEFUN(cfg_nat_ussd_lst_name,
+      cfg_nat_ussd_lst_name_cmd,
+      "ussd-list-name NAME",
+      "Set the name of the access list to check for IMSIs for USSD message\n"
+      "The name of the access list for HLR USSD handling")
+{
+	bsc_replace_string(_nat, &_nat->ussd_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_query,
+      cfg_nat_ussd_query_cmd,
+      "ussd-query QUERY",
+      "Set the USSD query to match with the ussd-list-name\n"
+      "The query to match")
+{
+	bsc_replace_string(_nat, &_nat->ussd_query, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_token,
+      cfg_nat_ussd_token_cmd,
+      "ussd-token TOKEN",
+      "Set the token used to identify the USSD module\n" "Secret key\n")
+{
+	bsc_replace_string(_nat, &_nat->ussd_token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_local,
+      cfg_nat_ussd_local_cmd,
+      "ussd-local-ip A.B.C.D",
+      "Set the IP to listen for the USSD Provider\n" "IP Address\n")
+{
+	bsc_replace_string(_nat, &_nat->ussd_local, argv[0]);
+	return CMD_SUCCESS;
+}
+
+/* per BSC configuration */
+DEFUN(cfg_bsc, cfg_bsc_cmd, "bsc BSC_NR", "Select a BSC to configure")
+{
+	int bsc_nr = atoi(argv[0]);
+	struct bsc_config *bsc;
+
+	if (bsc_nr > _nat->num_bsc) {
+		vty_out(vty, "%% The next unused BSC number is %u%s",
+			_nat->num_bsc, VTY_NEWLINE);
+		return CMD_WARNING;
+	} else if (bsc_nr == _nat->num_bsc) {
+		/* allocate a new one */
+		bsc = bsc_config_alloc(_nat, "unknown");
+	} else
+		bsc = bsc_config_num(_nat, bsc_nr);
+
+	if (!bsc)
+		return CMD_WARNING;
+
+	vty->index = bsc;
+	vty->node = NAT_BSC_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_token, cfg_bsc_token_cmd, "token TOKEN", "Set the token")
+{
+	struct bsc_config *conf = vty->index;
+
+	bsc_replace_string(conf, &conf->token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_lac, cfg_bsc_lac_cmd, "location_area_code <0-65535>",
+      "Set the Location Area Code (LAC) of this BSC")
+{
+	struct bsc_config *tmp;
+	struct bsc_config *conf = vty->index;
+
+	int lac = atoi(argv[0]);
+
+	if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+		vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+			lac, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* verify that the LACs are unique */
+	llist_for_each_entry(tmp, &_nat->bsc_configs, entry) {
+		if (bsc_config_handles_lac(tmp, lac)) {
+			vty_out(vty, "%% LAC %d is already used.%s", lac, VTY_NEWLINE);
+			return CMD_ERR_INCOMPLETE;
+		}
+	}
+
+	bsc_config_add_lac(conf, lac);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_lac, cfg_bsc_no_lac_cmd,
+      "no location_area_code <0-65535>",
+      NO_STR "Set the Location Area Code (LAC) of this BSC")
+{
+	int lac = atoi(argv[0]);
+	struct bsc_config *conf = vty->index;
+
+	bsc_config_del_lac(conf, lac);
+	return CMD_SUCCESS;
+}
+
+
+
+DEFUN(cfg_lst_imsi_allow,
+      cfg_lst_imsi_allow_cmd,
+      "access-list NAME imsi-allow [REGEXP]",
+      "Allow IMSIs matching the REGEXP\n"
+      "The name of the access-list\n"
+      "The regexp of allowed IMSIs\n")
+{
+	struct bsc_nat_acc_lst *acc;
+	struct bsc_nat_acc_lst_entry *entry;
+
+	acc = bsc_nat_acc_lst_get(_nat, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	entry = bsc_nat_acc_lst_entry_create(acc);
+	if (!entry)
+		return CMD_WARNING;
+
+	bsc_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, argc - 1, &argv[1]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_lst_imsi_deny,
+      cfg_lst_imsi_deny_cmd,
+      "access-list NAME imsi-deny [REGEXP]",
+      "Allow IMSIs matching the REGEXP\n"
+      "The name of the access-list\n"
+      "The regexp of to be denied IMSIs\n")
+{
+	struct bsc_nat_acc_lst *acc;
+	struct bsc_nat_acc_lst_entry *entry;
+
+	acc = bsc_nat_acc_lst_get(_nat, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	entry = bsc_nat_acc_lst_entry_create(acc);
+	if (!entry)
+		return CMD_WARNING;
+
+	bsc_parse_reg(acc, &entry->imsi_deny_re, &entry->imsi_deny, argc - 1, &argv[1]);
+	return CMD_SUCCESS;
+}
+
+/* naming to follow Zebra... */
+DEFUN(cfg_lst_no,
+      cfg_lst_no_cmd,
+      "no access-list NAME",
+      NO_STR "Remove an access-list by name\n"
+      "The access-list to remove\n")
+{
+	struct bsc_nat_acc_lst *acc;
+	acc = bsc_nat_acc_lst_find(_nat, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	bsc_nat_acc_lst_delete(acc);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_acc_lst,
+      show_acc_lst_cmd,
+      "show access-list NAME",
+      SHOW_STR "The name of the access list\n")
+{
+	struct bsc_nat_acc_lst *acc;
+	acc = bsc_nat_acc_lst_find(_nat, argv[0]);
+	if (!acc)
+		return CMD_WARNING;
+
+	vty_out(vty, "access-list %s%s", acc->name, VTY_NEWLINE);
+	vty_out_rate_ctr_group(vty, " ", acc->stats);
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bsc_acc_lst_name,
+      cfg_bsc_acc_lst_name_cmd,
+      "access-list-name NAME",
+      "Set the name of the access list to use.\n"
+      "The name of the to be used access list.")
+{
+	struct bsc_config *conf = vty->index;
+
+	bsc_replace_string(conf, &conf->acc_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_max_endps, cfg_bsc_max_endps_cmd,
+      "max-endpoints <1-1024>",
+      "Highest endpoint to use (exclusively)\n" "Number of ports\n")
+{
+	struct bsc_config *conf = vty->index;
+
+	conf->max_endpoints = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_paging,
+      cfg_bsc_paging_cmd,
+      "paging forbidden (0|1)",
+      "Forbid sending PAGING REQUESTS to the BSC.")
+{
+	struct bsc_config *conf = vty->index;
+
+	if (strcmp("1", argv[0]) == 0)
+		conf->forbid_paging = 1;
+	else
+		conf->forbid_paging = 0;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_desc,
+      cfg_bsc_desc_cmd,
+      "description DESC",
+      "Provide a description for the given BSC.")
+{
+	struct bsc_config *conf = vty->index;
+
+	bsc_replace_string(conf, &conf->description, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(test_regex, test_regex_cmd,
+      "test regex PATTERN STRING",
+      "Check if the string is matching the current pattern.")
+{
+	regex_t reg;
+	char *str = NULL;
+
+	memset(&reg, 0, sizeof(reg));
+	bsc_parse_reg(_nat, &reg, &str, 1, argv);
+
+	vty_out(vty, "String matches allow pattern: %d%s",
+		regexec(&reg, argv[1], 0, NULL, 0) == 0, VTY_NEWLINE);
+
+	talloc_free(str);
+	regfree(&reg);
+	return CMD_SUCCESS;
+}
+
+DEFUN(set_last_endp, set_last_endp_cmd,
+      "set bsc last-used-endpoint <0-9999999999> <0-1024>",
+      "Set a value\n" "Operate on a BSC\n"
+      "Last used endpoint for an assignment\n" "BSC configuration number\n"
+      "Endpoint number used\n")
+{
+	struct bsc_connection *con;
+	int nr = atoi(argv[0]);
+	int endp = atoi(argv[1]);
+
+
+	llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+		if (!con->cfg)
+			continue;
+		if (con->cfg->nr != nr)
+			continue;
+
+		con->last_endpoint = endp;
+		vty_out(vty, "Updated the last endpoint for %d to %d.%s",
+			con->cfg->nr, con->last_endpoint, VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+int bsc_nat_vty_init(struct bsc_nat *nat)
+{
+	_nat = nat;
+
+	/* show commands */
+	install_element_ve(&show_sccp_cmd);
+	install_element_ve(&show_bsc_cmd);
+	install_element_ve(&show_bsc_cfg_cmd);
+	install_element_ve(&show_stats_cmd);
+	install_element_ve(&show_stats_lac_cmd);
+	install_element_ve(&close_bsc_cmd);
+	install_element_ve(&show_msc_cmd);
+	install_element_ve(&test_regex_cmd);
+	install_element_ve(&show_bsc_mgcp_cmd);
+	install_element_ve(&show_acc_lst_cmd);
+
+	install_element(ENABLE_NODE, &set_last_endp_cmd);
+
+	/* nat group */
+	install_element(CONFIG_NODE, &cfg_nat_cmd);
+	install_node(&nat_node, config_write_nat);
+	install_default(NAT_NODE);
+	install_element(NAT_NODE, &ournode_exit_cmd);
+	install_element(NAT_NODE, &ournode_end_cmd);
+	install_element(NAT_NODE, &cfg_nat_msc_ip_cmd);
+	install_element(NAT_NODE, &cfg_nat_msc_port_cmd);
+	install_element(NAT_NODE, &cfg_nat_auth_time_cmd);
+	install_element(NAT_NODE, &cfg_nat_ping_time_cmd);
+	install_element(NAT_NODE, &cfg_nat_pong_time_cmd);
+	install_element(NAT_NODE, &cfg_nat_token_cmd);
+	install_element(NAT_NODE, &cfg_nat_bsc_ip_dscp_cmd);
+	install_element(NAT_NODE, &cfg_nat_bsc_ip_tos_cmd);
+	install_element(NAT_NODE, &cfg_nat_acc_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_query_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_token_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_local_cmd);
+
+	/* access-list */
+	install_element(NAT_NODE, &cfg_lst_imsi_allow_cmd);
+	install_element(NAT_NODE, &cfg_lst_imsi_deny_cmd);
+	install_element(NAT_NODE, &cfg_lst_no_cmd);
+
+	/* number rewriting */
+	install_element(NAT_NODE, &cfg_nat_number_rewrite_cmd);
+
+	/* BSC subgroups */
+	install_element(NAT_NODE, &cfg_bsc_cmd);
+	install_node(&bsc_node, config_write_bsc);
+	install_default(NAT_BSC_NODE);
+	install_element(NAT_BSC_NODE, &ournode_exit_cmd);
+	install_element(NAT_BSC_NODE, &ournode_end_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_token_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_lac_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_no_lac_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_paging_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_desc_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
+	install_element(NAT_BSC_NODE, &cfg_bsc_max_endps_cmd);
+
+	mgcp_vty_init();
+
+	return 0;
+}
+
+
+/* called by the telnet interface... we have our own init above */
+int bsc_vty_init(void)
+{
+	return 0;
+}
diff --git a/src/osmo-bsc_nat/bsc_sccp.c b/src/osmo-bsc_nat/bsc_sccp.c
new file mode 100644
index 0000000..72de112
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_sccp.c
@@ -0,0 +1,249 @@
+/* SCCP patching and handling routines */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocore/talloc.h>
+
+#include <string.h>
+#include <time.h>
+
+static int equal(struct sccp_source_reference *ref1, struct sccp_source_reference *ref2)
+{
+	return memcmp(ref1, ref2, sizeof(*ref1)) == 0;
+}
+
+/*
+ * SCCP patching below
+ */
+
+/* check if we are using this ref for patched already */
+static int sccp_ref_is_free(struct sccp_source_reference *ref, struct bsc_nat *nat)
+{
+	struct sccp_connections *conn;
+
+	llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+		if (memcmp(ref, &conn->patched_ref, sizeof(*ref)) == 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+/* copied from sccp.c */
+static int assign_src_local_reference(struct sccp_source_reference *ref, struct bsc_nat *nat)
+{
+	static uint32_t last_ref = 0x50000;
+	int wrapped = 0;
+
+	do {
+		struct sccp_source_reference reference;
+		reference.octet1 = (last_ref >>  0) & 0xff;
+		reference.octet2 = (last_ref >>  8) & 0xff;
+		reference.octet3 = (last_ref >> 16) & 0xff;
+
+		++last_ref;
+		/* do not use the reversed word and wrap around */
+		if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) {
+			LOGP(DNAT, LOGL_NOTICE, "Wrapped searching for a free code\n");
+			last_ref = 0;
+			++wrapped;
+		}
+
+		if (sccp_ref_is_free(&reference, nat) == 0) {
+			*ref = reference;
+			return 0;
+		}
+	} while (wrapped != 2);
+
+	LOGP(DNAT, LOGL_ERROR, "Finding a free reference failed\n");
+	return -1;
+}
+
+struct sccp_connections *create_sccp_src_ref(struct bsc_connection *bsc,
+					     struct bsc_nat_parsed *parsed)
+{
+	struct sccp_connections *conn;
+
+	/* Some commercial BSCs like to reassign there SRC ref */
+	llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+		if (conn->bsc != bsc)
+			continue;
+		if (memcmp(&conn->real_ref, parsed->src_local_ref, sizeof(conn->real_ref)) != 0)
+			continue;
+
+		/* the BSC has reassigned the SRC ref and we failed to keep track */
+		memset(&conn->remote_ref, 0, sizeof(conn->remote_ref));
+		if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) {
+			LOGP(DNAT, LOGL_ERROR, "BSC %d reused src ref: %d and we failed to generate a new id.\n",
+			     bsc->cfg->nr, sccp_src_ref_to_int(parsed->src_local_ref));
+			bsc_mgcp_dlcx(conn);
+			llist_del(&conn->list_entry);
+			talloc_free(conn);
+			return NULL;
+		} else {
+			clock_gettime(CLOCK_MONOTONIC, &conn->creation_time);
+			bsc_mgcp_dlcx(conn);
+			return conn;
+		}
+	}
+
+
+	conn = talloc_zero(bsc->nat, struct sccp_connections);
+	if (!conn) {
+		LOGP(DNAT, LOGL_ERROR, "Memory allocation failure.\n");
+		return NULL;
+	}
+
+	conn->bsc = bsc;
+	clock_gettime(CLOCK_MONOTONIC, &conn->creation_time);
+	conn->real_ref = *parsed->src_local_ref;
+	if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to assign a ref.\n");
+		talloc_free(conn);
+		return NULL;
+	}
+
+	bsc_mgcp_init(conn);
+	llist_add_tail(&conn->list_entry, &bsc->nat->sccp_connections);
+	rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_SCCP_CONN]);
+	counter_inc(bsc->cfg->nat->stats.sccp.conn);
+
+	LOGP(DNAT, LOGL_DEBUG, "Created 0x%x <-> 0x%x mapping for con %p\n",
+	     sccp_src_ref_to_int(&conn->real_ref),
+	     sccp_src_ref_to_int(&conn->patched_ref), bsc);
+
+	return conn;
+}
+
+int update_sccp_src_ref(struct sccp_connections *sccp, struct bsc_nat_parsed *parsed)
+{
+	if (!parsed->dest_local_ref || !parsed->src_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "CC MSG should contain both local and dest address.\n");
+		return -1;
+	}
+
+	sccp->remote_ref = *parsed->src_local_ref;
+	sccp->has_remote_ref = 1;
+	LOGP(DNAT, LOGL_DEBUG, "Updating 0x%x to remote 0x%x on %p\n",
+	     sccp_src_ref_to_int(&sccp->patched_ref),
+	     sccp_src_ref_to_int(&sccp->remote_ref), sccp->bsc);
+
+	return 0;
+}
+
+void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+	struct sccp_connections *conn;
+
+	llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+		if (memcmp(parsed->src_local_ref,
+			   &conn->patched_ref, sizeof(conn->patched_ref)) == 0) {
+
+			sccp_connection_destroy(conn);
+			return;
+		}
+	}
+
+	LOGP(DNAT, LOGL_ERROR, "Can not remove connection: 0x%x\n",
+	     sccp_src_ref_to_int(parsed->src_local_ref));
+}
+
+/*
+ * We have a message from the MSC to the BSC. The MSC is using
+ * an address that was assigned by the MUX, we need to update the
+ * dest reference to the real network.
+ */
+struct sccp_connections *patch_sccp_src_ref_to_bsc(struct msgb *msg,
+						   struct bsc_nat_parsed *parsed,
+						   struct bsc_nat *nat)
+{
+	struct sccp_connections *conn;
+
+	if (!parsed->dest_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "MSG should contain dest_local_ref.\n");
+		return NULL;
+	}
+
+
+	llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+		if (!equal(parsed->dest_local_ref, &conn->patched_ref))
+			continue;
+
+		/* Change the dest address to the real one */
+		*parsed->dest_local_ref = conn->real_ref;
+		return conn;
+	}
+
+	return NULL;
+}
+
+/*
+ * These are message to the MSC. We will need to find the BSC
+ * Connection by either the SRC or the DST local reference.
+ *
+ * In case of a CR we need to work by the SRC local reference
+ * in all other cases we need to work by the destination local
+ * reference..
+ */
+struct sccp_connections *patch_sccp_src_ref_to_msc(struct msgb *msg,
+						   struct bsc_nat_parsed *parsed,
+						   struct bsc_connection *bsc)
+{
+	struct sccp_connections *conn;
+
+	llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+		if (conn->bsc != bsc)
+			continue;
+
+		if (parsed->src_local_ref) {
+			if (equal(parsed->src_local_ref, &conn->real_ref)) {
+				*parsed->src_local_ref = conn->patched_ref;
+				return conn;
+			}
+		} else if (parsed->dest_local_ref) {
+			if (equal(parsed->dest_local_ref, &conn->remote_ref))
+				return conn;
+		} else {
+			LOGP(DNAT, LOGL_ERROR, "Header has neither loc/dst ref.\n");
+			return NULL;
+		}
+	}
+
+	return NULL;
+}
+
+struct sccp_connections *bsc_nat_find_con_by_bsc(struct bsc_nat *nat,
+						 struct sccp_source_reference *ref)
+{
+	struct sccp_connections *conn;
+
+	llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+		if (memcmp(ref, &conn->real_ref, sizeof(*ref)) == 0)
+			return conn;
+	}
+
+	return NULL;
+}
diff --git a/src/osmo-bsc_nat/bsc_ussd.c b/src/osmo-bsc_nat/bsc_ussd.c
new file mode 100644
index 0000000..c121abe
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_ussd.c
@@ -0,0 +1,363 @@
+/* USSD Filter Code */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+
+#include <osmocore/protocol/gsm_08_08.h>
+#include <osmocore/gsm0480.h>
+#include <osmocore/talloc.h>
+#include <osmocore/tlv.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <sys/socket.h>
+#include <string.h>
+#include <unistd.h>
+
+struct bsc_nat_ussd_con {
+	struct write_queue queue;
+	struct bsc_nat *nat;
+	int authorized;
+
+	struct timer_list auth_timeout;
+};
+
+static void ussd_auth_con(struct tlv_parsed *, struct bsc_nat_ussd_con *);
+
+static struct bsc_nat_ussd_con *bsc_nat_ussd_alloc(struct bsc_nat *nat)
+{
+	struct bsc_nat_ussd_con *con;
+
+	con = talloc_zero(nat, struct bsc_nat_ussd_con);
+	if (!con)
+		return NULL;
+
+	con->nat = nat;
+	return con;
+}
+
+static void bsc_nat_ussd_destroy(struct bsc_nat_ussd_con *con)
+{
+	if (con->nat->ussd_con == con) {
+		bsc_close_ussd_connections(con->nat);
+		con->nat->ussd_con = NULL;
+	}
+
+	close(con->queue.bfd.fd);
+	bsc_unregister_fd(&con->queue.bfd);
+	bsc_del_timer(&con->auth_timeout);
+	write_queue_clear(&con->queue);
+	talloc_free(con);
+}
+
+static int forward_sccp(struct bsc_nat *nat, struct msgb *msg)
+{
+	struct sccp_connections *con;
+	struct bsc_nat_parsed *parsed;
+
+
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from USSD.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	if (!parsed->dest_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "No destination local reference.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	con = bsc_nat_find_con_by_bsc(nat, parsed->dest_local_ref);
+	if (!con || !con->bsc) {
+		LOGP(DNAT, LOGL_ERROR, "No active connection found.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	talloc_free(parsed);
+	bsc_write_msg(&con->bsc->write_queue, msg);
+	return 0;
+}
+
+static int ussd_read_cb(struct bsc_fd *bfd)
+{
+	int error;
+	struct bsc_nat_ussd_con *conn = bfd->data;
+	struct msgb *msg = ipaccess_read_msg(bfd, &error);
+	struct ipaccess_head *hh;
+
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "USSD Connection was lost.\n");
+		bsc_nat_ussd_destroy(conn);
+		return -1;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "MSG from USSD: %s proto: %d\n",
+		hexdump(msg->data, msg->len), msg->l2h[0]);
+	hh = (struct ipaccess_head *) msg->data;
+
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_ID_RESP) {
+			struct tlv_parsed tvp;
+			ipaccess_idtag_parse(&tvp,
+					     (unsigned char *) msg->l2h + 2,
+					     msgb_l2len(msg) - 2);
+			if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME))
+				ussd_auth_con(&tvp, conn);
+		}
+
+		msgb_free(msg);
+	} else if (hh->proto == IPAC_PROTO_SCCP) {
+		forward_sccp(conn->nat, msg);
+	} else {
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+static void ussd_auth_cb(void *_data)
+{
+	LOGP(DNAT, LOGL_ERROR, "USSD module didn't authenticate\n");
+	bsc_nat_ussd_destroy((struct bsc_nat_ussd_con *) _data);
+}
+
+static void ussd_auth_con(struct tlv_parsed *tvp, struct bsc_nat_ussd_con *conn)
+{
+	const char *token;
+	int len;
+	if (!conn->nat->ussd_token) {
+		LOGP(DNAT, LOGL_ERROR, "No USSD token set. Closing\n");
+		bsc_nat_ussd_destroy(conn);
+		return;
+	}
+
+	token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+ 	len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+	if (strncmp(conn->nat->ussd_token, token, len) != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Wrong USSD token by client: %d\n",
+			conn->queue.bfd.fd);
+		bsc_nat_ussd_destroy(conn);
+		return;
+	}
+
+	/* it is authenticated now */
+	if (conn->nat->ussd_con && conn->nat->ussd_con != conn)
+		bsc_nat_ussd_destroy(conn->nat->ussd_con);
+
+	LOGP(DNAT, LOGL_ERROR, "USSD token specified. USSD provider is connected.\n");
+	bsc_del_timer(&conn->auth_timeout);
+	conn->authorized = 1;
+	conn->nat->ussd_con = conn;
+}
+
+static void ussd_start_auth(struct bsc_nat_ussd_con *conn)
+{
+	struct msgb *msg;
+
+	conn->auth_timeout.data = conn;
+	conn->auth_timeout.cb = ussd_auth_cb;
+	bsc_schedule_timer(&conn->auth_timeout, conn->nat->auth_timeout, 0);
+
+	msg = msgb_alloc_headroom(4096, 128, "auth message");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate auth msg\n");
+		return;
+	}
+
+	msgb_v_put(msg, IPAC_MSGT_ID_GET);
+	bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS);
+}
+
+static int ussd_listen_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct bsc_nat_ussd_con *conn;
+	struct bsc_nat *nat;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+	int fd;
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		perror("accept");
+		return fd;
+	}
+
+	nat = (struct bsc_nat *) bfd->data;
+	counter_inc(nat->stats.ussd.reconn);
+
+	conn = bsc_nat_ussd_alloc(nat);
+	if (!conn) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate USSD con struct.\n");
+		close(fd);
+		return -1;
+	}
+
+	write_queue_init(&conn->queue, 10);
+	conn->queue.bfd.data = conn;
+	conn->queue.bfd.fd = fd;
+	conn->queue.bfd.when = BSC_FD_READ;
+	conn->queue.read_cb = ussd_read_cb;
+	conn->queue.write_cb = bsc_write_cb;
+
+	if (bsc_register_fd(&conn->queue.bfd) < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to register USSD fd.\n");
+		bsc_nat_ussd_destroy(conn);
+		return -1;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "USSD Connection on %d with IP: %s\n",
+	     fd, inet_ntoa(sa.sin_addr));
+
+	/* do authentication */
+	ussd_start_auth(conn);
+	return 0;
+}
+
+int bsc_ussd_init(struct bsc_nat *nat)
+{
+	struct in_addr addr;
+
+	addr.s_addr = INADDR_ANY;
+	if (nat->ussd_local)
+		inet_aton(nat->ussd_local, &addr);
+
+	nat->ussd_listen.data = nat;
+	return make_sock(&nat->ussd_listen, IPPROTO_TCP,
+			 ntohl(addr.s_addr), 5001, ussd_listen_cb);
+}
+
+static int forward_ussd(struct sccp_connections *con, const struct ussd_request *req,
+			struct msgb *input)
+{
+	struct msgb *msg, *copy;
+	struct ipac_msgt_sccp_state *state;
+	struct bsc_nat_ussd_con *ussd;
+
+	if (!con->bsc->nat->ussd_con)
+		return -1;
+
+	msg = msgb_alloc_headroom(4096, 128, "forward ussd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		return -1;
+	}
+
+	copy = msgb_alloc_headroom(4096, 128, "forward bts");
+	if (!copy) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	copy->l2h = msgb_put(copy, msgb_l2len(input));
+	memcpy(copy->l2h, input->l2h, msgb_l2len(input));
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = IPAC_MSGT_SCCP_OLD;
+
+	/* fill out the data */
+	state = (struct ipac_msgt_sccp_state *) msgb_put(msg, sizeof(*state));
+	state->trans_id = req->transaction_id;
+	state->invoke_id = req->invoke_id;
+	memcpy(&state->src_ref, &con->remote_ref, sizeof(con->remote_ref));
+	memcpy(&state->dst_ref, &con->real_ref, sizeof(con->real_ref));
+	memcpy(state->imsi, con->imsi, strlen(con->imsi));
+
+	ussd = con->bsc->nat->ussd_con;
+	bsc_do_write(&ussd->queue, msg, IPAC_PROTO_IPACCESS);
+	bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP);
+
+	return 0;
+}
+
+int bsc_check_ussd(struct sccp_connections *con, struct bsc_nat_parsed *parsed,
+		   struct msgb *msg)
+{
+	uint32_t len;
+	uint8_t msg_type;
+	struct gsm48_hdr *hdr48;
+	struct bsc_nat_acc_lst *lst;
+	struct ussd_request req;
+
+	/*
+	 * various checks to avoid the decoding work. Right now we only want to
+	 * decode if the connection was created for USSD, we do have a USSD access
+	 * list, a query, a IMSI and such...
+	 */
+	if (con->con_type != NAT_CON_TYPE_SSA)
+		return 0;
+
+	if (!con->imsi)
+		return 0;
+
+	if (!con->bsc->nat->ussd_lst_name)
+		return 0;
+	if (!con->bsc->nat->ussd_query)
+		return 0;
+
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return 0;
+
+	if (strlen(con->imsi) > GSM_IMSI_LENGTH)
+		return 0;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return 0;
+
+	msg_type = hdr48->msg_type & 0xbf;
+	if (hdr48->proto_discr != GSM48_PDISC_NC_SS || msg_type != GSM0480_MTYPE_REGISTER)
+		return 0;
+
+	/* now check if it is a IMSI we care about */
+	lst = bsc_nat_acc_lst_find(con->bsc->nat, con->bsc->nat->ussd_lst_name);
+	if (!lst)
+		return 0;
+
+	if (bsc_nat_lst_check_allow(lst, con->imsi) != 0)
+		return 0;
+
+	/* now decode the message and see if we really want to handle it */
+	memset(&req, 0, sizeof(req));
+	if (gsm0480_decode_ussd_request(hdr48, len, &req) != 1)
+		return 0;
+	if (req.text[0] == 0xff)
+		return 0;
+
+	if (strcmp(req.text, con->bsc->nat->ussd_query) != 0)
+		return 0;
+
+	/* found a USSD query for our subscriber */
+	LOGP(DNAT, LOGL_NOTICE, "Found USSD query for %s\n", con->imsi);
+	if (forward_ussd(con, &req, msg) != 0)
+		return 0;
+	return 1;
+}
diff --git a/src/osmo-nitb/Makefile.am b/src/osmo-nitb/Makefile.am
new file mode 100644
index 0000000..44cb023
--- /dev/null
+++ b/src/osmo-nitb/Makefile.am
@@ -0,0 +1,14 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = osmo-nitb
+
+osmo_nitb_SOURCES = bsc_hack.c
+osmo_nitb_LDADD = -ldl -ldbi $(LIBCRYPT) $(LIBOSMOVTY_LIBS) \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmsc/libmsc.a \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libtrau/libtrau.a \
+		$(top_builddir)/src/libabis/libabis.a \
+		$(top_builddir)/src/libcommon/libcommon.a
diff --git a/src/osmo-nitb/Makefile.in b/src/osmo-nitb/Makefile.in
new file mode 100644
index 0000000..8e94435
--- /dev/null
+++ b/src/osmo-nitb/Makefile.in
@@ -0,0 +1,496 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = osmo-nitb$(EXEEXT)
+subdir = src/osmo-nitb
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_osmo_nitb_OBJECTS = bsc_hack.$(OBJEXT)
+osmo_nitb_OBJECTS = $(am_osmo_nitb_OBJECTS)
+am__DEPENDENCIES_1 =
+osmo_nitb_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libabis/libabis.a \
+	$(top_builddir)/src/libcommon/libcommon.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(osmo_nitb_SOURCES)
+DIST_SOURCES = $(osmo_nitb_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+osmo_nitb_SOURCES = bsc_hack.c
+osmo_nitb_LDADD = -ldl -ldbi $(LIBCRYPT) $(LIBOSMOVTY_LIBS) \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmsc/libmsc.a \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libtrau/libtrau.a \
+		$(top_builddir)/src/libabis/libabis.a \
+		$(top_builddir)/src/libcommon/libcommon.a
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/osmo-nitb/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/osmo-nitb/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+osmo-nitb$(EXEEXT): $(osmo_nitb_OBJECTS) $(osmo_nitb_DEPENDENCIES) 
+	@rm -f osmo-nitb$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(osmo_nitb_OBJECTS) $(osmo_nitb_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_hack.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/osmo-nitb/bsc_hack.c b/src/osmo-nitb/bsc_hack.c
new file mode 100644
index 0000000..357ec7a
--- /dev/null
+++ b/src/osmo-nitb/bsc_hack.c
@@ -0,0 +1,313 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/db.h>
+#include <osmocore/select.h>
+#include <osmocore/process.h>
+#include <openbsc/debug.h>
+#include <openbsc/e1_input.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/sms_queue.h>
+#include <openbsc/vty.h>
+
+#include "../../bscconfig.h"
+
+/* MCC and MNC for the Location Area Identifier */
+static struct log_target *stderr_target;
+struct gsm_network *bsc_gsmnet = 0;
+static const char *database_name = "hlr.sqlite3";
+static const char *config_file = "openbsc.cfg";
+extern const char *openbsc_copyright;
+static int daemonize = 0;
+static int use_mncc_sock = 0;
+
+/* timer to store statistics */
+#define DB_SYNC_INTERVAL	60, 0
+static struct timer_list db_sync_timer;
+
+extern int bsc_bootstrap_network(int (*mncc_recv)(struct gsm_network *, struct msgb *),
+				 const char *cfg_file);
+extern int bsc_shutdown_net(struct gsm_network *net);
+
+static void create_pcap_file(char *file)
+{
+	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+	int fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, mode);
+
+	if (fd < 0) {
+		perror("Failed to open file for pcap");
+		return;
+	}
+
+	e1_set_pcap_fd(fd);
+}
+
+static void print_usage()
+{
+	printf("Usage: bsc_hack\n");
+}
+
+static void print_help()
+{
+	printf("  Some useful help...\n");
+	printf("  -h --help this text\n");
+	printf("  -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+	printf("  -D --daemonize Fork the process into a background daemon\n");
+	printf("  -c --config-file filename The config file to use.\n");
+	printf("  -s --disable-color\n");
+	printf("  -l --database db-name The database to use\n");
+	printf("  -a --authorize-everyone. Authorize every new subscriber. Dangerous!.\n");
+	printf("  -p --pcap file  The filename of the pcap file\n");
+	printf("  -T --timestamp Prefix every log line with a timestamp\n");
+	printf("  -V --version. Print the version of OpenBSC.\n");
+	printf("  -P --rtp-proxy Enable the RTP Proxy code inside OpenBSC\n");
+	printf("  -e --log-level number. Set a global loglevel.\n");
+	printf("  -m --mncc-sock Disable built-in MNCC handler and offer socket\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	while (1) {
+		int option_index = 0, c;
+		static struct option long_options[] = {
+			{"help", 0, 0, 'h'},
+			{"debug", 1, 0, 'd'},
+			{"daemonize", 0, 0, 'D'},
+			{"config-file", 1, 0, 'c'},
+			{"disable-color", 0, 0, 's'},
+			{"database", 1, 0, 'l'},
+			{"authorize-everyone", 0, 0, 'a'},
+			{"pcap", 1, 0, 'p'},
+			{"timestamp", 0, 0, 'T'},
+			{"version", 0, 0, 'V' },
+			{"rtp-proxy", 0, 0, 'P'},
+			{"log-level", 1, 0, 'e'},
+			{"mncc-sock", 0, 0, 'm'},
+			{0, 0, 0, 0}
+		};
+
+		c = getopt_long(argc, argv, "hd:Dsl:ar:p:TPVc:e:m",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_usage();
+			print_help();
+			exit(0);
+		case 's':
+			log_set_use_color(stderr_target, 0);
+			break;
+		case 'd':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 'D':
+			daemonize = 1;
+			break;
+		case 'l':
+			database_name = strdup(optarg);
+			break;
+		case 'c':
+			config_file = strdup(optarg);
+			break;
+		case 'p':
+			create_pcap_file(optarg);
+			break;
+		case 'T':
+			log_set_print_timestamp(stderr_target, 1);
+			break;
+		case 'P':
+			ipacc_rtp_direct = 0;
+			break;
+		case 'e':
+			log_set_log_level(stderr_target, atoi(optarg));
+			break;
+		case 'm':
+			use_mncc_sock = 1;
+			break;
+		case 'V':
+			print_version(1);
+			exit(0);
+			break;
+		default:
+			/* ignore */
+			break;
+		}
+	}
+}
+
+extern void *tall_vty_ctx;
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "signal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		bsc_shutdown_net(bsc_gsmnet);
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+		sleep(3);
+		exit(0);
+		break;
+	case SIGABRT:
+		/* in case of abort, we want to obtain a talloc report
+		 * and then return to the caller, who will abort the process */
+	case SIGUSR1:
+		talloc_report(tall_vty_ctx, stderr);
+		talloc_report_full(tall_bsc_ctx, stderr);
+		break;
+	case SIGUSR2:
+		talloc_report_full(tall_vty_ctx, stderr);
+		break;
+	default:
+		break;
+	}
+}
+
+/* timer handling */
+static int _db_store_counter(struct counter *counter, void *data)
+{
+	return db_store_counter(counter);
+}
+
+static void db_sync_timer_cb(void *data)
+{
+	/* store counters to database and re-schedule */
+	counters_for_each(_db_store_counter, NULL);
+	bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL);
+}
+
+extern int bts_model_unknown_init(void);
+extern int bts_model_bs11_init(void);
+extern int bts_model_nanobts_init(void);
+extern int bts_model_rbs2k_init(void);
+extern int bts_model_hslfemto_init(void);
+void talloc_ctx_init(void);
+
+extern enum node_type bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+	.name 		= "OpenBSC",
+	.version	= PACKAGE_VERSION,
+	.go_parent_cb	= bsc_vty_go_parent,
+	.is_config_node	= bsc_vty_is_config_node,
+};
+
+int main(int argc, char **argv)
+{
+	int rc;
+
+	vty_info.copyright = openbsc_copyright;
+
+	log_init(&log_info);
+	tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
+	talloc_ctx_init();
+	on_dso_load_token();
+	on_dso_load_rrlp();
+	on_dso_load_ho_dec();
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+
+	bts_model_unknown_init();
+	bts_model_bs11_init();
+	bts_model_nanobts_init();
+	bts_model_rbs2k_init();
+	bts_model_hslfemto_init();
+
+	e1inp_init();
+
+	/* enable filters */
+	log_set_all_filter(stderr_target, 1);
+
+	/* This needs to precede handle_options() */
+	vty_init(&vty_info);
+	bsc_vty_init();
+
+	/* parse options */
+	handle_options(argc, argv);
+
+	/* internal MNCC handler or MNCC socket? */
+	if (use_mncc_sock) {
+		rc = bsc_bootstrap_network(mncc_sock_from_cc, config_file);
+		if (rc >= 0)
+			mncc_sock_init(bsc_gsmnet);
+	} else
+		rc = bsc_bootstrap_network(int_mncc_recv, config_file);
+	if (rc < 0)
+		exit(1);
+	bsc_api_init(bsc_gsmnet, msc_bsc_api());
+	mncc_sock_init(bsc_gsmnet);
+
+	/* seed the PRNG */
+	srand(time(NULL));
+
+	if (db_init(database_name)) {
+		printf("DB: Failed to init database. Please check the option settings.\n");
+		return -1;
+	}
+	printf("DB: Database initialized.\n");
+
+	if (db_prepare()) {
+		printf("DB: Failed to prepare database.\n");
+		return -1;
+	}
+	printf("DB: Database prepared.\n");
+
+	/* setup the timer */
+	db_sync_timer.cb = db_sync_timer_cb;
+	db_sync_timer.data = NULL;
+	bsc_schedule_timer(&db_sync_timer, DB_SYNC_INTERVAL);
+
+	signal(SIGINT, &signal_handler);
+	signal(SIGABRT, &signal_handler);
+	signal(SIGUSR1, &signal_handler);
+	signal(SIGUSR2, &signal_handler);
+	signal(SIGPIPE, SIG_IGN);
+
+	/* start the SMS queue */
+	if (sms_queue_start(bsc_gsmnet, 20) != 0)
+		return -1;
+
+	if (daemonize) {
+		rc = osmo_daemonize();
+		if (rc < 0) {
+			perror("Error during daemonize");
+			exit(1);
+		}
+	}
+
+	while (1) {
+		log_reset_context();
+		bsc_select_main(0);
+	}
+}
diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am
new file mode 100644
index 0000000..2351f8a
--- /dev/null
+++ b/src/utils/Makefile.am
@@ -0,0 +1,12 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = bs11_config isdnsync
+
+bs11_config_SOURCES = bs11_config.c rs232.c
+bs11_config_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		    $(top_builddir)/src/libabis/libabis.a \
+		    $(top_builddir)/src/libbsc/libbsc.a
+
+isdnsync_SOURCES = isdnsync.c
diff --git a/src/utils/Makefile.in b/src/utils/Makefile.in
new file mode 100644
index 0000000..17e8ad6
--- /dev/null
+++ b/src/utils/Makefile.in
@@ -0,0 +1,496 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+bin_PROGRAMS = bs11_config$(EXEEXT) isdnsync$(EXEEXT)
+subdir = src/utils
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_bs11_config_OBJECTS = bs11_config.$(OBJEXT) rs232.$(OBJEXT)
+bs11_config_OBJECTS = $(am_bs11_config_OBJECTS)
+bs11_config_DEPENDENCIES = $(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libabis/libabis.a \
+	$(top_builddir)/src/libbsc/libbsc.a
+am_isdnsync_OBJECTS = isdnsync.$(OBJEXT)
+isdnsync_OBJECTS = $(am_isdnsync_OBJECTS)
+isdnsync_LDADD = $(LDADD)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(bs11_config_SOURCES) $(isdnsync_SOURCES)
+DIST_SOURCES = $(bs11_config_SOURCES) $(isdnsync_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+bs11_config_SOURCES = bs11_config.c rs232.c
+bs11_config_LDADD = $(top_builddir)/src/libcommon/libcommon.a \
+		    $(top_builddir)/src/libabis/libabis.a \
+		    $(top_builddir)/src/libbsc/libbsc.a
+
+isdnsync_SOURCES = isdnsync.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/utils/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/utils/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p; \
+	  then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	      echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	      $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' `; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+bs11_config$(EXEEXT): $(bs11_config_OBJECTS) $(bs11_config_DEPENDENCIES) 
+	@rm -f bs11_config$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(bs11_config_OBJECTS) $(bs11_config_LDADD) $(LIBS)
+isdnsync$(EXEEXT): $(isdnsync_OBJECTS) $(isdnsync_DEPENDENCIES) 
+	@rm -f isdnsync$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(isdnsync_OBJECTS) $(isdnsync_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bs11_config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/isdnsync.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rs232.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+	clean-generic ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am uninstall-binPROGRAMS
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/utils/bs11_config.c b/src/utils/bs11_config.c
new file mode 100644
index 0000000..eaed8b7
--- /dev/null
+++ b/src/utils/bs11_config.c
@@ -0,0 +1,918 @@
+/* Siemens BS-11 microBTS configuration tool */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This software is based on ideas (but not code) of BS11Config
+ * (C) 2009 by Dieter Spaar <spaar@mirider.augusta.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <osmocore/select.h>
+#include <openbsc/rs232.h>
+
+/* state of our bs11_config application */
+enum bs11cfg_state {
+	STATE_NONE,
+	STATE_LOGON_WAIT,
+	STATE_LOGON_ACK,
+	STATE_SWLOAD,
+	STATE_QUERY,
+};
+static enum bs11cfg_state bs11cfg_state = STATE_NONE;
+static char *command, *value;
+struct timer_list status_timer;
+
+static const u_int8_t obj_li_attr[] = {
+	NM_ATT_BS11_BIT_ERR_THESH, 0x09, 0x00,
+	NM_ATT_BS11_L1_PROT_TYPE, 0x00,
+	NM_ATT_BS11_LINE_CFG, 0x00,
+};
+static const u_int8_t obj_bbsig0_attr[] = {
+	NM_ATT_BS11_RSSI_OFFS, 0x02, 0x00, 0x00,
+	NM_ATT_BS11_DIVERSITY, 0x01, 0x00,
+};
+static const u_int8_t obj_pa0_attr[] = {
+	NM_ATT_BS11_TXPWR, 0x01, BS11_TRX_POWER_GSM_30mW,
+};
+static const char *trx1_password = "1111111111";
+#define TEI_OML	25
+
+static const u_int8_t too_fast[] = { 0x12, 0x80, 0x00, 0x00, 0x02, 0x02 };
+
+static struct log_target *stderr_target;
+
+/* dummy function to keep gsm_data.c happy */
+struct counter *counter_alloc(const char *name)
+{
+	return NULL;
+}
+
+int handle_serial_msg(struct msgb *rx_msg);
+
+/* create all objects for an initial configuration */
+static int create_objects(struct gsm_bts *bts)
+{
+	fprintf(stdout, "Crating Objects for minimal config\n");
+	abis_nm_bs11_create_object(bts, BS11_OBJ_LI, 0, sizeof(obj_li_attr),
+				   obj_li_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_GPSU, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_ALCO, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_CCLK, 0, 0, NULL);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 0,
+				   sizeof(obj_bbsig0_attr), obj_bbsig0_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 0,
+				   sizeof(obj_pa0_attr), obj_pa0_attr);
+	abis_nm_bs11_create_envaBTSE(bts, 0);
+	abis_nm_bs11_create_envaBTSE(bts, 1);
+	abis_nm_bs11_create_envaBTSE(bts, 2);
+	abis_nm_bs11_create_envaBTSE(bts, 3);
+
+	abis_nm_bs11_conn_oml_tei(bts, 0, 1, 0xff, TEI_OML);
+
+	abis_nm_bs11_set_trx_power(bts->c0, BS11_TRX_POWER_GSM_30mW);
+	
+	sleep(1);
+
+	abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+	sleep(1);
+
+	return 0;
+}
+
+static int create_trx1(struct gsm_bts *bts)
+{
+	u_int8_t bbsig1_attr[sizeof(obj_bbsig0_attr)+12];
+	u_int8_t *cur = bbsig1_attr;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 1);
+
+	if (!trx)
+		trx = gsm_bts_trx_alloc(bts);
+
+	fprintf(stdout, "Crating Objects for TRX1\n");
+
+	abis_nm_bs11_set_trx1_pw(bts, trx1_password);
+
+	sleep(1);
+
+	cur = tlv_put(cur, NM_ATT_BS11_PASSWORD, 10,
+		      (u_int8_t *)trx1_password);
+	memcpy(cur, obj_bbsig0_attr, sizeof(obj_bbsig0_attr));
+	abis_nm_bs11_create_object(bts, BS11_OBJ_BBSIG, 1,
+				   sizeof(bbsig1_attr), bbsig1_attr);
+	abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 1,
+				   sizeof(obj_pa0_attr), obj_pa0_attr);
+	abis_nm_bs11_set_trx_power(trx, BS11_TRX_POWER_GSM_30mW);
+	
+	return 0;
+}
+
+static char *serial_port = "/dev/ttyUSB0";
+static char *fname_safety = "BTSBMC76.SWI";
+static char *fname_software = "HS011106.SWL";
+static int delay_ms = 0;
+static int win_size = 8;
+static int param_disconnect = 0;
+static int param_restart = 0;
+static int param_forced = 0;
+static struct gsm_bts *g_bts;
+
+static int file_is_readable(const char *fname)
+{
+	int rc;
+	struct stat st;
+
+	rc = stat(fname, &st);
+	if (rc < 0)
+		return 0;
+
+	if (S_ISREG(st.st_mode) && (st.st_mode & S_IRUSR))
+		return 1;
+
+	return 0;
+}
+
+static int percent;
+static int percent_old;
+
+/* callback function passed to the ABIS OML code */
+static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *msg,
+		       void *data, void *param)
+{
+	if (hook != GSM_HOOK_NM_SWLOAD)
+		return 0;
+
+	switch (event) {
+	case NM_MT_LOAD_INIT_ACK:
+		fprintf(stdout, "Software Load Initiate ACK\n");
+		break;
+	case NM_MT_LOAD_INIT_NACK:
+		fprintf(stderr, "ERROR: Software Load Initiate NACK\n");
+		exit(5);
+		break;
+	case NM_MT_LOAD_END_ACK:
+		if (data) {
+			/* we did a safety load and must activate it */
+			abis_nm_software_activate(g_bts, fname_safety,
+						  swload_cbfn, g_bts);
+			sleep(5);
+		}
+		break;
+	case NM_MT_LOAD_END_NACK:
+		fprintf(stderr, "ERROR: Software Load End NACK\n");
+		exit(3);
+		break;
+	case NM_MT_ACTIVATE_SW_NACK:
+		fprintf(stderr, "ERROR: Activate Software NACK\n");
+		exit(4);
+		break;
+	case NM_MT_ACTIVATE_SW_ACK:
+		bs11cfg_state = STATE_NONE;
+		
+		break;
+	case NM_MT_LOAD_SEG_ACK:
+		percent = abis_nm_software_load_status(g_bts);
+		if (percent > percent_old)
+			printf("Software Download Progress: %d%%\n", percent);
+		percent_old = percent;
+		break;
+	}
+	return 0;
+}
+
+static const char *bs11_link_state[] = {
+	[0x00]	= "Down",
+	[0x01]	= "Up",
+	[0x02]	= "Restoring",
+};
+
+static const char *linkstate_name(u_int8_t linkstate)
+{
+	if (linkstate > ARRAY_SIZE(bs11_link_state))
+		return "Unknown";
+
+	return bs11_link_state[linkstate];
+}
+
+static const char *mbccu_load[] = {
+	[0]	= "No Load",
+	[1]	= "Load BTSCAC",
+	[2]	= "Load BTSDRX",
+	[3]	= "Load BTSBBX",
+	[4]	= "Load BTSARC",
+	[5]	= "Load",
+};
+
+static const char *mbccu_load_name(u_int8_t linkstate)
+{
+	if (linkstate > ARRAY_SIZE(mbccu_load))
+		return "Unknown";
+
+	return mbccu_load[linkstate];
+}
+
+static const char *bts_phase_name(u_int8_t phase)
+{
+	switch (phase) {
+	case BS11_STATE_WARM_UP:
+	case BS11_STATE_WARM_UP_2:
+		return "Warm Up";
+		break;
+	case BS11_STATE_LOAD_SMU_SAFETY:
+		return "Load SMU Safety";
+		break;
+	case BS11_STATE_LOAD_SMU_INTENDED:
+		return "Load SMU Intended";
+		break;
+	case BS11_STATE_LOAD_MBCCU:
+		return "Load MBCCU";
+		break;
+	case BS11_STATE_SOFTWARE_RQD:
+		return "Software required";
+		break;
+	case BS11_STATE_WAIT_MIN_CFG:
+	case BS11_STATE_WAIT_MIN_CFG_2:
+		return "Wait minimal config";
+		break;
+	case BS11_STATE_MAINTENANCE:
+		return "Maintenance";
+		break;
+	case BS11_STATE_NORMAL:
+		return "Normal";
+		break;
+	case BS11_STATE_ABIS_LOAD:
+		return "Abis load";
+		break;
+	default:
+		return "Unknown";
+		break;
+	}
+}
+
+static const char *trx_power_name(u_int8_t pwr)
+{
+	switch (pwr) {
+	case BS11_TRX_POWER_GSM_2W:	
+		return "2W (GSM)";
+	case BS11_TRX_POWER_GSM_250mW:
+		return "250mW (GSM)";
+	case BS11_TRX_POWER_GSM_80mW:
+		return "80mW (GSM)";
+	case BS11_TRX_POWER_GSM_30mW:
+		return "30mW (GSM)";
+	case BS11_TRX_POWER_DCS_3W:
+		return "3W (DCS)";
+	case BS11_TRX_POWER_DCS_1W6:
+		return "1.6W (DCS)";
+	case BS11_TRX_POWER_DCS_500mW:
+		return "500mW (DCS)";
+	case BS11_TRX_POWER_DCS_160mW:
+		return "160mW (DCS)";
+	default:
+		return "unknown value";
+	}
+}
+
+static const char *pll_mode_name(u_int8_t mode)
+{
+	switch (mode) {
+	case BS11_LI_PLL_LOCKED:
+		return "E1 Locked";
+	case BS11_LI_PLL_STANDALONE:
+		return "Standalone";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *cclk_acc_name(u_int8_t acc)
+{
+	switch (acc) {
+	case 0:
+		/* Out of the demanded +/- 0.05ppm */
+		return "Medium";
+	case 1:
+		/* Synchronized with Abis, within demanded tolerance +/- 0.05ppm */
+		return "High";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *bport_lcfg_name(u_int8_t lcfg)
+{
+	switch (lcfg) {
+	case BS11_LINE_CFG_STAR:
+		return "Star";
+	case BS11_LINE_CFG_MULTIDROP:
+		return "Multi-Drop";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *obj_name(struct abis_om_fom_hdr *foh)
+{
+	static char retbuf[256];
+
+	retbuf[0] = 0;
+
+	switch (foh->obj_class) {
+	case NM_OC_BS11:
+		strcat(retbuf, "BS11 ");
+		switch (foh->obj_inst.bts_nr) {
+		case BS11_OBJ_PA:
+			sprintf(retbuf+strlen(retbuf), "Power Amplifier %d ",
+				foh->obj_inst.ts_nr);
+			break;
+		case BS11_OBJ_LI:
+			sprintf(retbuf+strlen(retbuf), "Line Interface ");
+			break;
+		case BS11_OBJ_CCLK:
+			sprintf(retbuf+strlen(retbuf), "CCLK ");
+			break;
+		}
+		break;
+	case NM_OC_SITE_MANAGER:
+		strcat(retbuf, "SITE MANAGER ");
+		break;
+	case NM_OC_BS11_BPORT:
+		sprintf(retbuf+strlen(retbuf), "BPORT%u ",
+			foh->obj_inst.bts_nr);
+		break;
+	}
+	return retbuf;
+}
+
+static void print_state(struct tlv_parsed *tp)
+{
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_BTS_STATE)) {
+		u_int8_t phase, mbccu;
+		if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 1) {
+			phase = *TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE);
+			printf("PHASE: %u %-20s ", phase & 0xf,
+				bts_phase_name(phase));
+		}
+		if (TLVP_LEN(tp, NM_ATT_BS11_BTS_STATE) >= 2) {
+			mbccu = *(TLVP_VAL(tp, NM_ATT_BS11_BTS_STATE)+1);
+			printf("MBCCU0: %-11s MBCCU1: %-11s ",
+				mbccu_load_name(mbccu & 0xf), mbccu_load_name(mbccu >> 4));
+		}
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_E1_STATE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_E1_STATE) >= 1) {
+		u_int8_t e1_state = *TLVP_VAL(tp, NM_ATT_BS11_E1_STATE);
+		printf("Abis-link: %-9s ", linkstate_name(e1_state & 0xf));
+	}
+	printf("\n");
+}
+
+static int print_attr(struct tlv_parsed *tp)
+{
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_PCB_SERIAL)) {
+		printf("\tBS-11 ESN PCB Serial Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_PCB_SERIAL));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_HW_CODE_NO)) {
+		printf("\tBS-11 ESN Hardware Code Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_HW_CODE_NO)+6);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_ESN_FW_CODE_NO)) {
+		printf("\tBS-11 ESN Firmware Code Number: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_ESN_FW_CODE_NO)+6);
+	}
+#if 0
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_BOOT_SW_VERS)) {
+		printf("BS-11 Boot Software Version: %s\n",
+			TLVP_VAL(tp, NM_ATT_BS11_BOOT_SW_VERS)+6);
+	}
+#endif
+	if (TLVP_PRESENT(tp, NM_ATT_ABIS_CHANNEL) &&
+	    TLVP_LEN(tp, NM_ATT_ABIS_CHANNEL) >= 3) {
+		const u_int8_t *chan = TLVP_VAL(tp, NM_ATT_ABIS_CHANNEL);
+		printf("\tE1 Channel: Port=%u Timeslot=%u ",
+			chan[0], chan[1]);
+		if (chan[2] == 0xff)
+			printf("(Full Slot)\n");
+		else
+			printf("Subslot=%u\n", chan[2]);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_TEI))
+		printf("\tTEI: %d\n", *TLVP_VAL(tp, NM_ATT_TEI));
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_TXPWR) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_TXPWR) >= 1) {
+		printf("\tTRX Power: %s\n",
+			trx_power_name(*TLVP_VAL(tp, NM_ATT_BS11_TXPWR)));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL_MODE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_PLL_MODE) >= 1) {
+		printf("\tPLL Mode: %s\n",
+			pll_mode_name(*TLVP_VAL(tp, NM_ATT_BS11_PLL_MODE)));
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_PLL) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_PLL) >= 4) {
+		const u_int8_t *vp = TLVP_VAL(tp, NM_ATT_BS11_PLL);
+		printf("\tPLL Set Value=%d, Work Value=%d\n",
+			vp[0] << 8 | vp[1], vp[2] << 8 | vp[3]);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_ACCURACY) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_CCLK_ACCURACY) >= 1) {
+		const u_int8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_ACCURACY);
+		printf("\tCCLK Accuracy: %s (%d)\n", cclk_acc_name(*acc), *acc);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_CCLK_TYPE) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_CCLK_TYPE) >= 1) {
+		const u_int8_t *acc = TLVP_VAL(tp, NM_ATT_BS11_CCLK_TYPE);
+		printf("\tCCLK Type=%d\n", *acc);
+	}
+	if (TLVP_PRESENT(tp, NM_ATT_BS11_LINE_CFG) &&
+	    TLVP_LEN(tp, NM_ATT_BS11_LINE_CFG) >= 1) {
+		const u_int8_t *lcfg = TLVP_VAL(tp, NM_ATT_BS11_LINE_CFG);
+		printf("\tLine Configuration: %s (%d)\n",
+			bport_lcfg_name(*lcfg), *lcfg);
+	}
+
+
+
+	return 0;
+}
+
+static void cmd_query(void)
+{
+	struct gsm_bts_trx *trx = g_bts->c0;
+
+	bs11cfg_state = STATE_QUERY;
+	abis_nm_bs11_get_serno(g_bts);
+	abis_nm_bs11_get_oml_tei_ts(g_bts);
+	abis_nm_bs11_get_pll_mode(g_bts);
+	abis_nm_bs11_get_cclk(g_bts);
+	abis_nm_bs11_get_trx_power(trx);
+	trx = gsm_bts_trx_num(g_bts, 1);
+	if (trx)
+		abis_nm_bs11_get_trx_power(trx);
+	abis_nm_bs11_get_bport_line_cfg(g_bts, 0);
+	abis_nm_bs11_get_bport_line_cfg(g_bts, 1);
+	sleep(1);
+	abis_nm_bs11_factory_logon(g_bts, 0);
+	command = NULL;
+}
+
+/* handle a response from the BTS to a GET STATE command */
+static int handle_state_resp(enum abis_bs11_phase state)
+{
+	int rc = 0;
+
+	switch (state) {
+	case BS11_STATE_WARM_UP:
+	case BS11_STATE_LOAD_SMU_SAFETY:
+	case BS11_STATE_LOAD_SMU_INTENDED:
+	case BS11_STATE_LOAD_MBCCU:
+		break;
+	case BS11_STATE_SOFTWARE_RQD:
+		bs11cfg_state = STATE_SWLOAD;
+		/* send safety load. Use g_bts as private 'param'
+		 * argument, so our swload_cbfn can distinguish
+		 * a safety load from a regular software */
+		if (file_is_readable(fname_safety))
+			rc = abis_nm_software_load(g_bts, 0xff, fname_safety,
+						   win_size, param_forced,
+						   swload_cbfn, g_bts);
+		else
+			fprintf(stderr, "No valid Safety Load file \"%s\"\n",
+				fname_safety);
+		break;
+	case BS11_STATE_WAIT_MIN_CFG:
+	case BS11_STATE_WAIT_MIN_CFG_2:
+		bs11cfg_state = STATE_SWLOAD;
+		rc = create_objects(g_bts);
+		break;
+	case BS11_STATE_MAINTENANCE:
+		if (command) {
+			if (!strcmp(command, "disconnect"))
+				abis_nm_bs11_factory_logon(g_bts, 0);
+			else if (!strcmp(command, "reconnect"))
+				rc = abis_nm_bs11_bsc_disconnect(g_bts, 1);
+			else if (!strcmp(command, "software")
+			    && bs11cfg_state != STATE_SWLOAD) {
+				bs11cfg_state = STATE_SWLOAD;
+				/* send software (FIXME: over A-bis?) */
+				if (file_is_readable(fname_software))
+					rc = abis_nm_bs11_load_swl(g_bts, fname_software,
+								   win_size, param_forced,
+								   swload_cbfn);
+				else
+					fprintf(stderr, "No valid Software file \"%s\"\n",
+						fname_software);
+			} else if (!strcmp(command, "delete-trx1")) {
+				printf("Locing BBSIG and PA objects of TRX1\n");
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+						      BS11_OBJ_BBSIG, 0, 1,
+						      NM_STATE_LOCKED);
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11,
+						      BS11_OBJ_PA, 0, 1,
+						      NM_STATE_LOCKED);
+				sleep(1);
+				printf("Deleting BBSIG and PA objects of TRX1\n");
+				abis_nm_bs11_delete_object(g_bts, BS11_OBJ_BBSIG, 1);
+				abis_nm_bs11_delete_object(g_bts, BS11_OBJ_PA, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "create-trx1")) {
+				create_trx1(g_bts);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-e1-locked")) {
+				abis_nm_bs11_set_pll_locked(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-standalone")) {
+				abis_nm_bs11_set_pll_locked(g_bts, 0);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-setvalue")) {
+				abis_nm_bs11_set_pll(g_bts, atoi(value));
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "pll-workvalue")) {
+				/* To set the work value we need to login as FIELD */
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				sleep(1);
+				abis_nm_bs11_infield_logon(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_set_pll(g_bts, atoi(value));
+				sleep(1);
+				abis_nm_bs11_infield_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "oml-tei")) {
+				abis_nm_bs11_conn_oml_tei(g_bts, 0, 1, 0xff, TEI_OML);
+				command = NULL;
+			} else if (!strcmp(command, "restart")) {
+				abis_nm_bs11_restart(g_bts);
+				command = NULL;
+			} else if (!strcmp(command, "query")) {
+				cmd_query();
+			} else if (!strcmp(command, "create-bport1")) {
+				abis_nm_bs11_create_bport(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "delete-bport1")) {
+				abis_nm_chg_adm_state(g_bts, NM_OC_BS11_BPORT, 1, 0xff, 0xff, NM_STATE_LOCKED);
+				sleep(1);
+				abis_nm_bs11_delete_bport(g_bts, 1);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport0-star")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_STAR);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport0-multidrop")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 0, BS11_LINE_CFG_MULTIDROP);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			} else if (!strcmp(command, "bport1-multidrop")) {
+				abis_nm_bs11_set_bport_line_cfg(g_bts, 1, BS11_LINE_CFG_MULTIDROP);
+				sleep(1);
+				abis_nm_bs11_factory_logon(g_bts, 0);
+				command = NULL;
+			}
+
+		}
+		break;
+	case BS11_STATE_NORMAL:
+		if (command) {
+			if (!strcmp(command, "reconnect"))
+				abis_nm_bs11_factory_logon(g_bts, 0);
+			else if (!strcmp(command, "disconnect"))
+				abis_nm_bs11_bsc_disconnect(g_bts, 0);
+			else if (!strcmp(command, "query")) {
+				cmd_query();
+			}
+		} else if (param_disconnect) {
+			param_disconnect = 0;
+			abis_nm_bs11_bsc_disconnect(g_bts, 0);
+			if (param_restart) {
+				param_restart = 0;
+				abis_nm_bs11_restart(g_bts);
+			}
+		}
+		break;
+	default:
+		break;
+	}
+	return rc;
+}
+
+/* handle a fully-received message/packet from the RS232 port */
+int handle_serial_msg(struct msgb *rx_msg)
+{
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	struct tlv_parsed tp;
+	int rc = -1;
+
+#if 0
+	if (rx_msg->len < LAPD_HDR_LEN
+			  + sizeof(struct abis_om_fom_hdr)
+			  + sizeof(struct abis_om_hdr)) {
+		if (!memcmp(rx_msg->data + 2, too_fast,
+			    sizeof(too_fast))) {
+			fprintf(stderr, "BS11 tells us we're too "
+				"fast, try --delay bigger than %u\n",
+				delay_ms);
+			return -E2BIG;
+		} else
+			fprintf(stderr, "unknown BS11 message\n");
+	}
+#endif
+
+	oh = (struct abis_om_hdr *) msgb_l2(rx_msg);
+	foh = (struct abis_om_fom_hdr *) oh->data;
+	switch (foh->msg_type) {
+	case NM_MT_BS11_LMT_LOGON_ACK:
+		printf("LMT LOGON: ACK\n\n");
+		if (bs11cfg_state == STATE_NONE)
+			bs11cfg_state = STATE_LOGON_ACK;
+		rc = abis_nm_bs11_get_state(g_bts);
+		break;
+	case NM_MT_BS11_LMT_LOGOFF_ACK:
+		printf("LMT LOGOFF: ACK\n");
+		exit(0);
+		break;
+	case NM_MT_BS11_GET_STATE_ACK:
+		rc = abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+		print_state(&tp);
+		if (TLVP_PRESENT(&tp, NM_ATT_BS11_BTS_STATE) &&
+		    TLVP_LEN(&tp, NM_ATT_BS11_BTS_STATE) >= 1)
+			rc = handle_state_resp(*TLVP_VAL(&tp, NM_ATT_BS11_BTS_STATE));
+		break;
+	case NM_MT_GET_ATTR_RESP:
+		printf("\n%sATTRIBUTES:\n", obj_name(foh));
+		abis_nm_tlv_parse(&tp, g_bts, foh->data, oh->length-sizeof(*foh));
+		rc = print_attr(&tp);
+		//hexdump(foh->data, oh->length-sizeof(*foh));
+		break;
+	case NM_MT_BS11_SET_ATTR_ACK:
+		printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) ACK\n",
+			foh->obj_class, foh->obj_inst.bts_nr,
+			foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+		rc = 0;
+		break;
+	case NM_MT_BS11_SET_ATTR_NACK:
+		printf("SET ATTRIBUTE ObjClass=0x%02x ObjInst=(%d,%d,%d) NACK\n",
+			foh->obj_class, foh->obj_inst.bts_nr,
+			foh->obj_inst.trx_nr, foh->obj_inst.ts_nr);
+		break;
+	case NM_MT_GET_ATTR_NACK:
+		printf("\n%sGET ATTR NACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_CREATE_OBJ_ACK:
+		printf("\n%sCREATE OBJECT ACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_CREATE_OBJ_NACK:
+		printf("\n%sCREATE OBJECT NACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_DELETE_OBJ_ACK:
+		printf("\n%sDELETE OBJECT ACK\n", obj_name(foh));
+		break;
+	case NM_MT_BS11_DELETE_OBJ_NACK:
+		printf("\n%sDELETE OBJECT NACK\n", obj_name(foh));
+		break;
+	default:
+		rc = abis_nm_rcvmsg(rx_msg);
+	}
+	if (rc < 0) {
+		perror("ERROR in main loop");
+		//break;
+	}
+	if (rc == 1)
+		return rc;
+
+	switch (bs11cfg_state) {
+	case STATE_NONE:
+		abis_nm_bs11_factory_logon(g_bts, 1);
+		break;
+	case STATE_LOGON_ACK:
+		bsc_schedule_timer(&status_timer, 5, 0);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+void status_timer_cb(void *data)
+{
+	abis_nm_bs11_get_state(g_bts);
+}
+
+static void print_banner(void)
+{
+	printf("bs11_config (C) 2009-2010 by Harald Welte and Dieter Spaar\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+}
+
+static void print_help(void)
+{
+	printf("bs11_config [options] [command]\n");
+	printf("\nSupported options:\n");
+	printf("\t-h --help\t\t\tPrint this help text\n");
+	printf("\t-p --port </dev/ttyXXX>\t\tSpecify serial port\n");
+	printf("\t-s --software <file>\t\tSpecify Software file\n");
+	printf("\t-S --safety <file>\t\tSpecify Safety Load file\n");
+	printf("\t-d --delay <ms>\t\t\tSpecify delay in milliseconds\n");
+	printf("\t-D --disconnect\t\t\tDisconnect BTS from BSC\n");
+	printf("\t-w --win-size <num>\t\tSpecify Window Size\n");
+	printf("\t-f --forced\t\t\tForce Software Load\n");
+	printf("\nSupported commands:\n");
+	printf("\tquery\t\t\tQuery the BS-11 about serial number and configuration\n");
+	printf("\tdisconnect\t\tDisconnect A-bis link (go into administrative state)\n");
+	printf("\tresconnect\t\tReconnect A-bis link (go into normal state)\n");
+	printf("\trestart\t\t\tRestart the BTS\n");
+	printf("\tsoftware\t\tDownload Software (only in administrative state)\n");
+	printf("\tcreate-trx1\t\tCreate objects for TRX1 (Danger: Your BS-11 might overheat)\n");
+	printf("\tdelete-trx1\t\tDelete objects for TRX1\n");
+	printf("\tpll-e1-locked\t\tSet the PLL to be locked to E1 clock\n");
+	printf("\tpll-standalone\t\tSet the PLL to be in standalone mode\n");
+	printf("\tpll-setvalue <value>\tSet the PLL set value\n");
+	printf("\tpll-workvalue <value>\tSet the PLL work value\n");
+	printf("\toml-tei\t\t\tSet OML E1 TS and TEI\n");
+	printf("\tbport0-star\t\tSet BPORT0 line config to star\n");
+	printf("\tbport0-multidrop\tSet BPORT0 line config to multidrop\n");
+	printf("\tbport1-multidrop\tSet BPORT1 line config to multidrop\n");
+	printf("\tcreate-bport1\t\tCreate BPORT1 object\n");
+	printf("\tdelete-bport1\t\tDelete BPORT1 object\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+	int option_index = 0;
+	print_banner();
+
+	while (1) {
+		int c;
+		static struct option long_options[] = {
+			{ "help", 0, 0, 'h' },
+			{ "port", 1, 0, 'p' },
+			{ "software", 1, 0, 's' },
+			{ "safety", 1, 0, 'S' },
+			{ "delay", 1, 0, 'd' },
+			{ "disconnect", 0, 0, 'D' },
+			{ "win-size", 1, 0, 'w' },
+			{ "forced", 0, 0, 'f' },
+			{ "restart", 0, 0, 'r' },
+			{ "debug", 1, 0, 'b'},
+		};
+
+		c = getopt_long(argc, argv, "hp:s:S:td:Dw:fra:",
+				long_options, &option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_help();
+			exit(0);
+		case 'p':
+			serial_port = optarg;
+			break;
+		case 'b':
+			log_parse_category_mask(stderr_target, optarg);
+			break;
+		case 's':
+			fname_software = optarg;
+			break;
+		case 'S':
+			fname_safety = optarg;
+			break;
+		case 'd':
+			delay_ms = atoi(optarg);
+			break;
+		case 'w':
+			win_size = atoi(optarg);
+			break;
+		case 'D':
+			param_disconnect = 1;
+			break;
+		case 'f':
+			param_forced = 1;
+			break;
+		case 'r':
+			param_disconnect = 1;
+			param_restart = 1;
+			break;
+		default:
+			break;
+		}
+	}
+	if (optind < argc)
+		command = argv[optind];
+	        if (optind+1 < argc)
+			value = argv[optind+1];
+
+}
+
+static int num_sigint;
+
+static void signal_handler(int signal)
+{
+	fprintf(stdout, "\nsignal %u received\n", signal);
+
+	switch (signal) {
+	case SIGINT:
+		num_sigint++;
+		abis_nm_bs11_factory_logon(g_bts, 0);
+		if (num_sigint >= 3)
+			exit(0);
+		break;
+	}
+}
+
+extern int bts_model_bs11_init(void);
+int main(int argc, char **argv)
+{
+	struct gsm_network *gsmnet;
+	int rc;
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	handle_options(argc, argv);
+	bts_model_bs11_init();
+
+	gsmnet = gsm_network_init(1, 1, NULL);
+	if (!gsmnet) {
+		fprintf(stderr, "Unable to allocate gsm network\n");
+		exit(1);
+	}
+	g_bts = gsm_bts_alloc(gsmnet, GSM_BTS_TYPE_BS11, HARDCODED_TSC,
+				HARDCODED_BSIC);
+
+	rc = rs232_setup(serial_port, delay_ms, g_bts);
+	if (rc < 0) {
+		fprintf(stderr, "Problem setting up serial port\n");
+		exit(1);
+	}
+
+	signal(SIGINT, &signal_handler);
+
+	abis_nm_bs11_factory_logon(g_bts, 1);
+	//abis_nm_bs11_get_serno(g_bts);
+
+	status_timer.cb = status_timer_cb;
+
+	while (1) {
+		bsc_select_main(0);
+	}
+
+	abis_nm_bs11_factory_logon(g_bts, 0);
+
+	exit(0);
+}
+
+/* dummy to be able to compile */
+void gsm_net_update_ctype(struct gsm_network *net)
+{
+}
diff --git a/src/utils/isdnsync.c b/src/utils/isdnsync.c
new file mode 100644
index 0000000..1c4aa5d
--- /dev/null
+++ b/src/utils/isdnsync.c
@@ -0,0 +1,191 @@
+/* isdnsync.c
+ *
+ * Author       Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All rights reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include "mISDNif.h"
+#define MISDN_OLD_AF_COMPATIBILITY
+#define AF_COMPATIBILITY_FUNC
+#include "compat_af_isdn.h"
+
+int card = 0;
+int sock = -1;
+
+int mISDN_open(void)
+{
+	int			fd, ret;
+	struct mISDN_devinfo	devinfo;
+	struct sockaddr_mISDN	l2addr;
+
+	fd = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE);
+	if (fd < 0) {
+		fprintf(stderr, "could not open socket (%s)\n", strerror(errno));
+		return fd;
+	}
+	devinfo.id = card;
+	ret = ioctl(fd, IMGETDEVINFO, &devinfo);
+	if (ret < 0) {
+		fprintf(stderr,"could not send IOCTL IMGETCOUNT (%s)\n", strerror(errno));
+		close(fd);
+		return ret;
+	}
+	close(fd);
+	if (!(devinfo.Dprotocols & (1 << ISDN_P_TE_S0))
+	 && !(devinfo.Dprotocols & (1 << ISDN_P_TE_E1))) {
+		fprintf(stderr,"Interface does not support TE mode (%s)\n", strerror(errno));
+		close(fd);
+		return ret;
+	}
+	fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_TE);
+	if (fd < 0) {
+		fprintf(stderr,"could not open ISDN_P_LAPD_TE socket (%s)\n", strerror(errno));
+		return fd;
+	}
+	l2addr.family = AF_ISDN;
+	l2addr.dev = card;
+	l2addr.channel = 0;
+	l2addr.sapi = 0;
+	l2addr.tei = 0;
+	ret = bind(fd, (struct sockaddr *)&l2addr, sizeof(l2addr));
+	if (ret < 0) {
+		fprintf(stderr,"could not bind socket for card %d (%s)\n", card, strerror(errno));
+		close(fd);
+		return ret;
+	}
+	sock = fd;
+
+	return sock;
+}
+
+
+void mISDN_handle(void)
+{
+	int ret;
+	fd_set rfd;
+	struct timeval tv;
+	struct sockaddr_mISDN addr;
+	socklen_t alen;
+	unsigned char buffer[2048];
+	struct mISDNhead *hh = (struct mISDNhead *)buffer;
+	int l1 = 0, l2 = 0, tei = 0;
+
+	while(1) {
+again:
+		FD_ZERO(&rfd);
+		FD_SET(sock, &rfd);
+		tv.tv_sec = 2;
+		tv.tv_usec = 0;
+		ret = select(sock+1, &rfd, NULL, NULL, &tv);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			fprintf(stderr, "%s aborted: %s\n", __FUNCTION__, strerror(errno));
+			break;
+		}
+		if (FD_ISSET(sock, &rfd)) {
+			alen = sizeof(addr);
+			ret = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &alen);
+			if (ret < 0) {
+				fprintf(stderr, "%s read socket error %s\n", __FUNCTION__, strerror(errno));
+			} else if (ret < MISDN_HEADER_LEN) {
+					fprintf(stderr, "%s read socket shor frame\n", __FUNCTION__);
+			} else {
+				switch(hh->prim) {
+					case MPH_ACTIVATE_IND:
+					case PH_ACTIVATE_IND:
+						if (!l1) {
+							printf("PH_ACTIVATE\n");
+							printf("*** Sync available from interface :-)\n");
+							l1 = 1;
+						}
+						goto again;
+					break;
+					case MPH_DEACTIVATE_IND:
+					case PH_DEACTIVATE_IND:
+						if (l1) {
+							printf("PH_DEACTIVATE\n");
+							printf("*** Lost sync on interface        :-(\n");
+							l1 = 0;
+						}
+						goto again;
+					break;
+					case DL_ESTABLISH_IND:
+					case DL_ESTABLISH_CNF:
+						printf("DL_ESTABLISH\n");
+						l2 = 1;
+						goto again;
+					break;
+					case DL_RELEASE_IND:
+					case DL_RELEASE_CNF:
+						printf("DL_RELEASE\n");
+						l2 = 0;
+						goto again;
+					break;
+					case DL_INFORMATION_IND:
+						printf("DL_INFORMATION (tei %d sapi %d)\n", addr.tei, addr.sapi);
+						tei = 1;
+					break;
+					default:
+//						printf("prim %x\n", hh->prim);
+						goto again;
+				}
+			}
+		}
+		if (tei && !l2) {
+			hh->prim = DL_ESTABLISH_REQ;
+			printf("-> activating layer 2\n");
+			sendto(sock, buffer, MISDN_HEADER_LEN, 0, (struct sockaddr *) &addr, alen);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	if (argc <= 1)
+	{
+		printf("Usage: %s <card>\n\n", argv[0]);
+		printf("Opens given card number in TE-mode PTP and tries to keep layer 2 established.\n");
+		printf("This keeps layer 1 activated to retrieve a steady sync signal from network.\n");
+		return(0);
+	}
+
+	card = atoi(argv[1]);
+
+	init_af_isdn();
+
+	if ((ret = mISDN_open() < 0))
+		return(ret);
+
+	mISDN_handle();
+
+	close(sock);
+
+	return 0;
+}
diff --git a/src/utils/rs232.c b/src/utils/rs232.c
new file mode 100644
index 0000000..7550571
--- /dev/null
+++ b/src/utils/rs232.c
@@ -0,0 +1,248 @@
+/* OpenBSC BS-11 T-Link interface using POSIX serial port */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <termios.h>
+#include <fcntl.h>
+
+#include <osmocore/select.h>
+#include <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/rs232.h>
+
+/* adaption layer from GSM 08.59 + 12.21 to RS232 */
+
+struct serial_handle {
+	struct bsc_fd fd;
+	struct llist_head tx_queue;
+
+	struct msgb *rx_msg;
+	unsigned int rxmsg_bytes_missing;
+
+	unsigned int delay_ms;
+	struct gsm_bts *bts;
+};
+
+/* FIXME: this needs to go */
+static struct serial_handle _ser_handle, *ser_handle = &_ser_handle;
+
+#define LAPD_HDR_LEN	10
+
+static int handle_ser_write(struct bsc_fd *bfd);
+
+/* callback from abis_nm */
+int _abis_nm_sendmsg(struct msgb *msg, int to_trx_oml)
+{
+	struct serial_handle *sh = ser_handle;
+	u_int8_t *lapd;
+	unsigned int len;
+
+	msg->l2h = msg->data;
+
+	/* prepend LAPD header */
+	lapd = msgb_push(msg, LAPD_HDR_LEN);
+
+	len = msg->len - 2;
+
+	lapd[0] = (len >> 8) & 0xff;
+	lapd[1] = len & 0xff; /* length of bytes startign at lapd[2] */
+	lapd[2] = 0x00;
+	lapd[3] = 0x07;
+	lapd[4] = 0x01;
+	lapd[5] = 0x3e;
+	lapd[6] = 0x00;
+	lapd[7] = 0x00;
+	lapd[8] = msg->len - 10; /* length of bytes starting at lapd[10] */
+	lapd[9] = lapd[8] ^ 0x38;
+
+	msgb_enqueue(&sh->tx_queue, msg);
+	sh->fd.when |= BSC_FD_WRITE;
+
+	/* we try to immediately send */
+	handle_ser_write(&sh->fd);
+
+	return 0;
+}
+
+/* select.c callback in case we can write to the RS232 */
+static int handle_ser_write(struct bsc_fd *bfd)
+{
+	struct serial_handle *sh = bfd->data;
+	struct msgb *msg;
+	int written;
+
+	msg = msgb_dequeue(&sh->tx_queue);
+	if (!msg) {
+		bfd->when &= ~BSC_FD_WRITE;
+		return 0;
+	}
+
+	DEBUGP(DMI, "RS232 TX: %s\n", hexdump(msg->data, msg->len));
+
+	/* send over serial line */
+	written = write(bfd->fd, msg->data, msg->len);
+	if (written < msg->len) {
+		perror("short write:");
+		msgb_free(msg);
+		return -1;
+	}
+
+	msgb_free(msg);
+	usleep(sh->delay_ms*1000);
+
+	return 0;
+}
+
+#define SERIAL_ALLOC_SIZE	300
+
+/* select.c callback in case we can read from the RS232 */
+static int handle_ser_read(struct bsc_fd *bfd)
+{
+	struct serial_handle *sh = bfd->data;
+	struct msgb *msg;
+	int rc = 0;
+
+	if (!sh->rx_msg) {
+		sh->rx_msg = msgb_alloc(SERIAL_ALLOC_SIZE, "RS232 Rx");
+		sh->rx_msg->l2h = NULL;
+		sh->rx_msg->trx = sh->bts->c0;
+	}
+	msg = sh->rx_msg;
+
+	/* first read two byes to obtain length */
+	if (msg->len < 2) {
+		rc = read(sh->fd.fd, msg->tail, 2 - msg->len);
+		if (rc < 0) {
+			perror("ERROR reading from serial port");
+			msgb_free(msg);
+			return rc;
+		}
+		msgb_put(msg, rc);
+
+		if (msg->len >= 2) {
+			/* parse LAPD payload length */
+			if (msg->data[0] != 0)
+				fprintf(stderr, "Suspicious header byte 0: 0x%02x\n",
+					msg->data[0]);
+
+			sh->rxmsg_bytes_missing = msg->data[0] << 8;
+			sh->rxmsg_bytes_missing += msg->data[1];
+
+			if (sh->rxmsg_bytes_missing < LAPD_HDR_LEN -2)
+				fprintf(stderr, "Invalid length in hdr: %u\n",
+					sh->rxmsg_bytes_missing);
+		}
+	} else {
+		/* try to read as many of the missing bytes as are available */
+		rc = read(sh->fd.fd, msg->tail, sh->rxmsg_bytes_missing);
+		if (rc < 0) {
+			perror("ERROR reading from serial port");
+			msgb_free(msg);
+			return rc;
+		}
+		msgb_put(msg, rc);
+		sh->rxmsg_bytes_missing -= rc;
+
+		if (sh->rxmsg_bytes_missing == 0) {
+			/* we have one complete message now */
+			sh->rx_msg = NULL;
+
+			if (msg->len > LAPD_HDR_LEN)
+				msg->l2h = msg->data + LAPD_HDR_LEN;
+
+			DEBUGP(DMI, "RS232 RX: %s\n", hexdump(msg->data, msg->len));
+			rc = handle_serial_msg(msg);
+		}
+	}
+
+	return rc;
+}
+
+/* select.c callback */
+static int serial_fd_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	int rc = 0;
+
+	if (what & BSC_FD_READ)
+		rc = handle_ser_read(bfd);
+
+	if (rc < 0)
+		return rc;
+
+	if (what & BSC_FD_WRITE)
+		rc = handle_ser_write(bfd);
+
+	return rc;
+}
+
+int rs232_setup(const char *serial_port, unsigned int delay_ms,
+		struct gsm_bts *bts)
+{
+	int rc, serial_fd;
+	struct termios tio;
+
+	serial_fd = open(serial_port, O_RDWR);
+	if (serial_fd < 0) {
+		perror("cannot open serial port:");
+		return serial_fd;
+	}
+
+	/* set baudrate */
+	rc = tcgetattr(serial_fd, &tio);
+	if (rc < 0) {
+		perror("tcgetattr()");
+		return rc;
+	}
+	cfsetispeed(&tio, B19200);
+	cfsetospeed(&tio, B19200);
+	tio.c_cflag |=  (CREAD | CLOCAL | CS8);
+	tio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
+	tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+	tio.c_iflag |=  (INPCK | ISTRIP);
+	tio.c_iflag &= ~(ISTRIP | IXON | IXOFF | IGNBRK | INLCR | ICRNL | IGNCR);
+	tio.c_oflag &= ~(OPOST);
+	rc = tcsetattr(serial_fd, TCSADRAIN, &tio);
+	if (rc < 0) {
+		perror("tcsetattr()");
+		return rc;
+	}
+
+	INIT_LLIST_HEAD(&ser_handle->tx_queue);
+	ser_handle->fd.fd = serial_fd;
+	ser_handle->fd.when = BSC_FD_READ;
+	ser_handle->fd.cb = serial_fd_cb;
+	ser_handle->fd.data = ser_handle;
+	ser_handle->delay_ms = delay_ms;
+	ser_handle->bts = bts;
+	rc = bsc_register_fd(&ser_handle->fd);
+	if (rc < 0) {
+		fprintf(stderr, "could not register FD: %s\n",
+			strerror(rc));
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..1968119
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = debug gsm0408 db channel mgcp
+
+if BUILD_NAT
+SUBDIRS += bsc-nat
+endif
diff --git a/tests/Makefile.in b/tests/Makefile.in
new file mode 100644
index 0000000..f5e61eb
--- /dev/null
+++ b/tests/Makefile.in
@@ -0,0 +1,538 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+@BUILD_NAT_TRUE@am__append_1 = bsc-nat
+subdir = tests
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+	html-recursive info-recursive install-data-recursive \
+	install-dvi-recursive install-exec-recursive \
+	install-html-recursive install-info-recursive \
+	install-pdf-recursive install-ps-recursive install-recursive \
+	installcheck-recursive installdirs-recursive pdf-recursive \
+	ps-recursive uninstall-recursive
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
+  distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+	$(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+	distdir
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = debug gsm0408 db channel mgcp bsc-nat
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = debug gsm0408 db channel mgcp $(am__append_1)
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+#     (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	target=`echo $@ | sed s/-recursive//`; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    dot_seen=yes; \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done; \
+	if test "$$dot_seen" = "no"; then \
+	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+	fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+	@fail= failcom='exit 1'; \
+	for f in x $$MAKEFLAGS; do \
+	  case $$f in \
+	    *=* | --[!k]*);; \
+	    *k*) failcom='fail=yes';; \
+	  esac; \
+	done; \
+	dot_seen=no; \
+	case "$@" in \
+	  distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+	  *) list='$(SUBDIRS)' ;; \
+	esac; \
+	rev=''; for subdir in $$list; do \
+	  if test "$$subdir" = "."; then :; else \
+	    rev="$$subdir $$rev"; \
+	  fi; \
+	done; \
+	rev="$$rev ."; \
+	target=`echo $@ | sed s/-recursive//`; \
+	for subdir in $$rev; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done && test -z "$$fail"
+tags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+	done
+ctags-recursive:
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+	done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+	  include_option=--etags-include; \
+	  empty_fix=.; \
+	else \
+	  include_option=--include; \
+	  empty_fix=; \
+	fi; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test ! -f $$subdir/TAGS || \
+	      set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+	  fi; \
+	done; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test -d "$(distdir)/$$subdir" \
+	    || $(MKDIR_P) "$(distdir)/$$subdir" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+	    $(am__relativize); \
+	    new_distdir=$$reldir; \
+	    dir1=$$subdir; dir2="$(top_distdir)"; \
+	    $(am__relativize); \
+	    new_top_distdir=$$reldir; \
+	    echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+	    echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+	    ($(am__cd) $$subdir && \
+	      $(MAKE) $(AM_MAKEFLAGS) \
+	        top_distdir="$$new_top_distdir" \
+	        distdir="$$new_distdir" \
+		am__remove_distdir=: \
+		am__skip_length_check=: \
+		am__skip_mode_fix=: \
+	        distdir) \
+	      || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic mostlyclean-am
+
+distclean: distclean-recursive
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \
+	install-am install-strip tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+	all all-am check check-am clean clean-generic ctags \
+	ctags-recursive distclean distclean-generic distclean-tags \
+	distdir dvi dvi-am html html-am info info-am install \
+	install-am install-data install-data-am install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am install-man \
+	install-pdf install-pdf-am install-ps install-ps-am \
+	install-strip installcheck installcheck-am installdirs \
+	installdirs-am maintainer-clean maintainer-clean-generic \
+	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
+	tags-recursive uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/bsc-nat/Makefile.am b/tests/bsc-nat/Makefile.am
new file mode 100644
index 0000000..04bdb97
--- /dev/null
+++ b/tests/bsc-nat/Makefile.am
@@ -0,0 +1,19 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+EXTRA_DIST = bsc_data.c
+
+noinst_PROGRAMS = bsc_nat_test
+
+bsc_nat_test_SOURCES = bsc_nat_test.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c
+bsc_nat_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+			$(top_srcdir)/src/libmgcp/libmgcp.a \
+			$(top_srcdir)/src/libabis/libabis.a \
+			$(top_srcdir)/src/libtrau/libtrau.a \
+			$(top_srcdir)/src/libcommon/libcommon.a \
+			$(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS)
diff --git a/tests/bsc-nat/Makefile.in b/tests/bsc-nat/Makefile.in
new file mode 100644
index 0000000..280d22d
--- /dev/null
+++ b/tests/bsc-nat/Makefile.in
@@ -0,0 +1,535 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = bsc_nat_test$(EXEEXT)
+subdir = tests/bsc-nat
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_bsc_nat_test_OBJECTS = bsc_nat_test.$(OBJEXT) bsc_filter.$(OBJEXT) \
+	bsc_sccp.$(OBJEXT) bsc_nat_utils.$(OBJEXT) \
+	bsc_mgcp_utils.$(OBJEXT)
+bsc_nat_test_OBJECTS = $(am_bsc_nat_test_OBJECTS)
+am__DEPENDENCIES_1 =
+bsc_nat_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+	$(top_srcdir)/src/libmgcp/libmgcp.a \
+	$(top_srcdir)/src/libabis/libabis.a \
+	$(top_srcdir)/src/libtrau/libtrau.a \
+	$(top_srcdir)/src/libcommon/libcommon.a $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_$(V))
+am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY))
+am__v_lt_0 = --silent
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(bsc_nat_test_SOURCES)
+DIST_SOURCES = $(bsc_nat_test_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+EXTRA_DIST = bsc_data.c
+bsc_nat_test_SOURCES = bsc_nat_test.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c \
+			$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c
+
+bsc_nat_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+			$(top_srcdir)/src/libmgcp/libmgcp.a \
+			$(top_srcdir)/src/libabis/libabis.a \
+			$(top_srcdir)/src/libtrau/libtrau.a \
+			$(top_srcdir)/src/libcommon/libcommon.a \
+			$(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/bsc-nat/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/bsc-nat/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+bsc_nat_test$(EXEEXT): $(bsc_nat_test_OBJECTS) $(bsc_nat_test_DEPENDENCIES) 
+	@rm -f bsc_nat_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(bsc_nat_test_OBJECTS) $(bsc_nat_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_filter.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_mgcp_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_test.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_nat_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_sccp.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+bsc_filter.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_filter.o -MD -MP -MF $(DEPDIR)/bsc_filter.Tpo -c -o bsc_filter.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_filter.Tpo $(DEPDIR)/bsc_filter.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' object='bsc_filter.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_filter.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c
+
+bsc_filter.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_filter.obj -MD -MP -MF $(DEPDIR)/bsc_filter.Tpo -c -o bsc_filter.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_filter.Tpo $(DEPDIR)/bsc_filter.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c' object='bsc_filter.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_filter.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_filter.c'; fi`
+
+bsc_sccp.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_sccp.o -MD -MP -MF $(DEPDIR)/bsc_sccp.Tpo -c -o bsc_sccp.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_sccp.Tpo $(DEPDIR)/bsc_sccp.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' object='bsc_sccp.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_sccp.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c
+
+bsc_sccp.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_sccp.obj -MD -MP -MF $(DEPDIR)/bsc_sccp.Tpo -c -o bsc_sccp.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_sccp.Tpo $(DEPDIR)/bsc_sccp.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c' object='bsc_sccp.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_sccp.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_sccp.c'; fi`
+
+bsc_nat_utils.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_nat_utils.o -MD -MP -MF $(DEPDIR)/bsc_nat_utils.Tpo -c -o bsc_nat_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_nat_utils.Tpo $(DEPDIR)/bsc_nat_utils.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' object='bsc_nat_utils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_nat_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c
+
+bsc_nat_utils.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_nat_utils.obj -MD -MP -MF $(DEPDIR)/bsc_nat_utils.Tpo -c -o bsc_nat_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_nat_utils.Tpo $(DEPDIR)/bsc_nat_utils.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c' object='bsc_nat_utils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_nat_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_nat_utils.c'; fi`
+
+bsc_mgcp_utils.o: $(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_mgcp_utils.o -MD -MP -MF $(DEPDIR)/bsc_mgcp_utils.Tpo -c -o bsc_mgcp_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_mgcp_utils.Tpo $(DEPDIR)/bsc_mgcp_utils.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' object='bsc_mgcp_utils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_mgcp_utils.o `test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' || echo '$(srcdir)/'`$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c
+
+bsc_mgcp_utils.obj: $(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT bsc_mgcp_utils.obj -MD -MP -MF $(DEPDIR)/bsc_mgcp_utils.Tpo -c -o bsc_mgcp_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/bsc_mgcp_utils.Tpo $(DEPDIR)/bsc_mgcp_utils.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c' object='bsc_mgcp_utils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o bsc_mgcp_utils.obj `if test -f '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; then $(CYGPATH_W) '$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/src/osmo-bsc_nat/bsc_mgcp_utils.c'; fi`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstPROGRAMS ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/bsc-nat/bsc_data.c b/tests/bsc-nat/bsc_data.c
new file mode 100644
index 0000000..0475523
--- /dev/null
+++ b/tests/bsc-nat/bsc_data.c
@@ -0,0 +1,187 @@
+/* test data */
+
+/* BSC -> MSC, CR */
+static const uint8_t bsc_cr[] = {
+0x00, 0x2e, 0xfd,
+0x01, 0x00, 0x00, 0x15, 0x02, 0x02, 0x04, 0x02,
+0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05,
+0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x1c, 0xc3,
+0x51, 0x17, 0x12, 0x05, 0x08, 0x20, 0x72, 0xf4,
+0x90, 0x20, 0x1d, 0x50, 0x08, 0x29, 0x47, 0x80,
+0x00, 0x00, 0x00, 0x00, 0x80, 0x00 };
+
+static const uint8_t bsc_cr_patched[] = {
+0x00, 0x2e, 0xfd,
+0x01, 0x00, 0x00, 0x05, 0x02, 0x02, 0x04, 0x02,
+0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05,
+0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x1c, 0xc3,
+0x51, 0x17, 0x12, 0x05, 0x08, 0x20, 0x72, 0xf4,
+0x90, 0x20, 0x1d, 0x50, 0x08, 0x29, 0x47, 0x80,
+0x00, 0x00, 0x00, 0x00, 0x80, 0x00 };
+
+/* CC, MSC -> BSC */
+static const uint8_t msc_cc[] = {
+0x00, 0x0a, 0xfd,
+0x02, 0x00, 0x00, 0x05, 0x01, 0x1f, 0xe4, 0x02,
+0x01, 0x00 };
+static const uint8_t msc_cc_patched[] = {
+0x00, 0x0a, 0xfd,
+0x02, 0x00, 0x00, 0x15, 0x01, 0x1f, 0xe4, 0x02,
+0x01, 0x00 };
+
+/* Classmark, BSC -> MSC */
+static const uint8_t bsc_dtap[] = {
+0x00, 0x17, 0xfd,
+0x06, 0x01, 0x1f, 0xe4, 0x00, 0x01, 0x10, 0x00,
+0x0e, 0x54, 0x12, 0x03, 0x50, 0x18, 0x93, 0x13,
+0x06, 0x60, 0x14, 0x45, 0x00, 0x81, 0x00 };
+
+static const uint8_t bsc_dtap_patched[] = {
+0x00, 0x17, 0xfd,
+0x06, 0x01, 0x1f, 0xe4, 0x00, 0x01, 0x10, 0x00,
+0x0e, 0x54, 0x12, 0x03, 0x50, 0x18, 0x93, 0x13,
+0x06, 0x60, 0x14, 0x45, 0x00, 0x81, 0x00 };
+
+/* Clear command, MSC -> BSC */
+static const uint8_t msc_dtap[] = {
+0x00, 0x0d, 0xfd,
+0x06, 0x00, 0x00, 0x05, 0x00, 0x01, 0x06, 0x00,
+0x04, 0x20, 0x04, 0x01, 0x09 };
+static const uint8_t msc_dtap_patched[] = {
+0x00, 0x0d, 0xfd,
+0x06, 0x00, 0x00, 0x15, 0x00, 0x01, 0x06, 0x00,
+0x04, 0x20, 0x04, 0x01, 0x09 };
+
+/*RLSD, MSC -> BSC */
+static const uint8_t msc_rlsd[] = {
+0x00, 0x0a, 0xfd,
+0x04, 0x00, 0x00, 0x05, 0x01, 0x1f, 0xe4, 0x00,
+0x01, 0x00 };
+static const uint8_t msc_rlsd_patched[] = {
+0x00, 0x0a, 0xfd,
+0x04, 0x00, 0x00, 0x15, 0x01, 0x1f, 0xe4, 0x00,
+0x01, 0x00 };
+
+/* RLC, BSC -> MSC */
+static const uint8_t bsc_rlc[] = {
+0x00, 0x07, 0xfd,
+0x05, 0x01, 0x1f, 0xe4, 0x00, 0x00, 0x15 };
+
+static const uint8_t bsc_rlc_patched[] = {
+0x00, 0x07, 0xfd,
+0x05, 0x01, 0x1f, 0xe4, 0x00, 0x00, 0x05 };
+
+
+/* a paging command */
+static const uint8_t paging_by_lac_cmd[] = {
+0x00, 0x22, 0xfd, 0x09,
+0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x02, 0x00,
+0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x12, 0x00,
+0x10, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10, 0x02,
+0x01, 0x50, 0x02, 0x30, 0x1a, 0x03, 0x05, 0x20,
+0x15 };
+
+/* an assignment command */
+static const uint8_t ass_cmd[] = {
+0x00, 0x12, 0xfd, 0x06,
+0x00, 0x00, 0x49, 0x00, 0x01, 0x0b, 0x00, 0x09,
+0x01, 0x0b, 0x03, 0x01, 0x0a, 0x11, 0x01, 0x00,
+0x01 };
+
+/* identity response */
+static const uint8_t id_resp[] = {
+0x00, 0x15, 0xfd, 0x06, 0x01, 0x1c, 0xdc,
+0x00, 0x01, 0x0e, 0x01, 0x00, 0x0b, 0x05, 0x59,
+0x08, 0x29, 0x40, 0x21, 0x03, 0x07, 0x48, 0x66,
+0x31
+};
+
+/*
+ * MGCP messages
+ */
+
+/* nothing to patch */
+static const char crcx[] = "CRCX 23265295 8@mgw MGCP 1.0\r\nC: 394b0439fb\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n";
+static const char crcx_patched[] = "CRCX 23265295 1e@mgw MGCP 1.0\r\nC: 394b0439fb\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n";
+
+
+/* patch the ip and port */
+static const char crcx_resp[] = "200 23265295\r\nI: 1\r\n\r\nv=0\r\nc=IN IP4 172.16.18.2\r\nm=audio 4002 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n";
+static const char crcx_resp_patched[] = "200 23265295\r\nI: 1\r\n\r\nv=0\r\nc=IN IP4 10.0.0.1\r\nm=audio 999 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n";
+
+/* patch the ip and port */
+static const char mdcx[] = "MDCX 23330829 8@mgw MGCP 1.0\r\nC: 394b0439fb\r\nI: 1\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 1049380491 0 IN IP4 172.16.18.2\r\ns=-\r\nc=IN IP4 172.16.18.2\r\nt=0 0\r\nm=audio 4410 RTP/AVP 126\r\na=rtpmap:126 AMR/8000/1\r\na=fmtp:126 mode-set=2;start-mode=0\r\na=ptime:20\r\na=recvonly\r\nm=image 4412 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n";
+static const char mdcx_patched[] = "MDCX 23330829 1e@mgw MGCP 1.0\r\nC: 394b0439fb\r\nI: 1\r\nL: p:20, a:AMR, nt:IN\r\nM: recvonly\r\n\r\nv=0\r\no=- 1049380491 0 IN IP4 172.16.18.2\r\ns=-\r\nc=IN IP4 10.0.0.23\r\nt=0 0\r\nm=audio 6666 RTP/AVP 126\r\na=rtpmap:126 AMR/8000/1\r\na=fmtp:126 mode-set=2;start-mode=0\r\na=ptime:20\r\na=recvonly\r\nm=image 4412 udptl t38\r\na=T38FaxVersion:0\r\na=T38MaxBitRate:14400\r\n";
+
+
+static const char mdcx_resp[] = "200 23330829\r\n\r\nv=0\r\nc=IN IP4 172.16.18.2\r\nm=audio 4002 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n";
+static const char mdcx_resp_patched[] = "200 23330829\r\n\r\nv=0\r\nc=IN IP4 10.0.0.23\r\nm=audio 5555 RTP/AVP 98\r\na=rtpmap:98 AMR/8000\r\n";
+
+/* different line ending */
+static const char mdcx_resp2[] = "200 33330829\n\nv=0\nc=IN IP4 172.16.18.2\nm=audio 4002 RTP/AVP 98\na=rtpmap:98 AMR/8000\n";
+static const char mdcx_resp_patched2[] = "200 33330829\n\nv=0\nc=IN IP4 10.0.0.23\nm=audio 5555 RTP/AVP 98\na=rtpmap:98 AMR/8000\n";
+
+struct mgcp_patch_test {
+	const char *orig;
+	const char *patch;
+	const char *ip;
+	const int port;
+};
+
+static const struct mgcp_patch_test mgcp_messages[] = {
+	{
+		.orig = crcx,
+		.patch = crcx_patched,
+		.ip = "0.0.0.0",
+		.port = 2323,
+	},
+	{
+		.orig = crcx_resp,
+		.patch = crcx_resp_patched,
+		.ip = "10.0.0.1",
+		.port = 999,
+	},
+	{
+		.orig = mdcx,
+		.patch = mdcx_patched,
+		.ip = "10.0.0.23",
+		.port = 6666,
+	},
+	{
+		.orig = mdcx_resp,
+		.patch = mdcx_resp_patched,
+		.ip = "10.0.0.23",
+		.port = 5555,
+	},
+	{
+		.orig = mdcx_resp2,
+		.patch = mdcx_resp_patched2,
+		.ip = "10.0.0.23",
+		.port = 5555,
+	},
+};
+
+/* CC Setup messages */
+static const uint8_t cc_setup_national[] = {
+	0x00, 0x20, 0xfd, 0x06, 0x01, 0x12,
+	0x6d, 0x00, 0x01, 0x19, 0x01, 0x00, 0x16, 0x03,
+	0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x06, 0x81, 0x10, 0x27, 0x33, 0x63,
+	0x66, 0x15, 0x02, 0x11, 0x01
+};
+
+static const uint8_t cc_setup_national_patched[] = {
+	0x00, 0x21, 0xfd, 0x06, 0x01, 0x12,
+	0x6d, 0x00, 0x01, 0x1a, 0x01, 0x00, 0x17, 0x03,
+	0x05, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x07, 0x91, 0x94, 0x71, 0x32, 0x33,
+	0x66, 0xf6, 0x15, 0x02, 0x11, 0x01
+};
+
+static const uint8_t cc_setup_international[] = {
+	0x00, 0x22, 0xfd, 0x06, 0x01, 0x13,
+	0xe7, 0x00, 0x01, 0x1b, 0x01, 0x00, 0x18, 0x03,
+	0x45, 0x04, 0x06, 0x60, 0x04, 0x02, 0x00, 0x05,
+	0x81, 0x5e, 0x08, 0x81, 0x00, 0x94, 0x71, 0x33,
+	0x63, 0x66, 0x03, 0x15, 0x02, 0x11, 0x01
+};
diff --git a/tests/bsc-nat/bsc_nat_test.c b/tests/bsc-nat/bsc_nat_test.c
new file mode 100644
index 0000000..504b691
--- /dev/null
+++ b/tests/bsc-nat/bsc_nat_test.c
@@ -0,0 +1,1016 @@
+/*
+ * BSC NAT Message filtering
+ *
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+
+#include <osmocore/talloc.h>
+
+#include <osmocom/sccp/sccp.h>
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <stdio.h>
+
+/* test messages for ipa */
+static uint8_t ipa_id[] = {
+	0x00, 0x01, 0xfe, 0x06,
+};
+
+/* SCCP messages are below */
+static uint8_t gsm_reset[] = {
+	0x00, 0x12, 0xfd,
+	0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+	0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+	0x01, 0x20,
+};
+
+static const uint8_t gsm_reset_ack[] = {
+	0x00, 0x13, 0xfd,
+	0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+	0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+	0x00, 0x01, 0x31,
+};
+
+static const uint8_t gsm_paging[] = {
+	0x00, 0x20, 0xfd,
+	0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+	0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10,
+	0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10,
+	0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06,
+};
+
+/* BSC -> MSC connection open */
+static const uint8_t bssmap_cr[] = {
+	0x00, 0x2c, 0xfd,
+	0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02,
+	0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05,
+	0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3,
+	0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33,
+	0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01,
+	0x31, 0x97, 0x61, 0x00
+};
+
+/* MSC -> BSC connection confirm */
+static const uint8_t bssmap_cc[] = {
+	0x00, 0x0a, 0xfd,
+	0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00,
+};
+
+/* MSC -> BSC released */
+static const uint8_t bssmap_released[] = {
+	0x00, 0x0e, 0xfd,
+	0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f,
+	0x02, 0x23, 0x42, 0x00,
+};
+
+/* BSC -> MSC released */
+static const uint8_t bssmap_release_complete[] = {
+	0x00, 0x07, 0xfd,
+	0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03
+};
+
+/* both directions IT timer */
+static const uint8_t connnection_it[] = {
+	0x00, 0x0b, 0xfd,
+	0x10, 0x01, 0x02, 0x03, 0x01, 0x02, 0x03,
+	0x00, 0x00, 0x00, 0x00,
+};
+
+/* error in both directions */
+static const uint8_t proto_error[] = {
+	0x00, 0x05, 0xfd,
+	0x0f, 0x22, 0x33, 0x44, 0x00,
+};
+
+/* MGCP wrap... */
+static const uint8_t mgcp_msg[] = {
+	0x00, 0x03, 0xfc,
+	0x20, 0x20, 0x20,
+};
+
+/* location updating request */
+static const uint8_t bss_lu[] = {
+	0x00, 0x2e, 0xfd,
+	0x01, 0x91, 0x45, 0x14, 0x02, 0x02, 0x04, 0x02,
+	0x42, 0xfe, 0x0f, 0x21, 0x00, 0x1f, 0x57, 0x05,
+	0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x14, 0xc3,
+	0x50, 0x17, 0x12, 0x05, 0x08, 0x70, 0x72, 0xf4,
+	0x80, 0xff, 0xfe, 0x30, 0x08, 0x29, 0x44, 0x50,
+	0x12, 0x03, 0x24, 0x01, 0x95, 0x00
+};
+
+/* paging response */
+static const uint8_t pag_resp[] = {
+	0x00, 0x2c, 0xfd, 0x01, 0xe5, 0x68,
+	0x14, 0x02, 0x02, 0x04, 0x02, 0x42, 0xfe, 0x0f,
+	0x1f, 0x00, 0x1d, 0x57, 0x05, 0x08, 0x00, 0x72,
+	0xf4, 0x80, 0x20, 0x16, 0xc3, 0x50, 0x17, 0x10,
+	0x06, 0x27, 0x01, 0x03, 0x30, 0x18, 0x96, 0x08,
+	0x29, 0x26, 0x30, 0x32, 0x11, 0x42, 0x01, 0x19,
+	0x00
+};
+
+struct filter_result {
+	const uint8_t *data;
+	const uint16_t length;
+	const int dir;
+	const int result;
+};
+
+static const struct filter_result results[] = {
+	{
+		.data = ipa_id,
+		.length = ARRAY_SIZE(ipa_id),
+		.dir = DIR_MSC,
+		.result = 1,
+	},
+	{
+		.data = gsm_reset,
+		.length = ARRAY_SIZE(gsm_reset),
+		.dir = DIR_MSC,
+		.result = 1,
+	},
+	{
+		.data = gsm_reset_ack,
+		.length = ARRAY_SIZE(gsm_reset_ack),
+		.dir = DIR_BSC,
+		.result = 1,
+	},
+	{
+		.data = gsm_paging,
+		.length = ARRAY_SIZE(gsm_paging),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_cr,
+		.length = ARRAY_SIZE(bssmap_cr),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_cc,
+		.length = ARRAY_SIZE(bssmap_cc),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_released,
+		.length = ARRAY_SIZE(bssmap_released),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = bssmap_release_complete,
+		.length = ARRAY_SIZE(bssmap_release_complete),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = mgcp_msg,
+		.length = ARRAY_SIZE(mgcp_msg),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = connnection_it,
+		.length = ARRAY_SIZE(connnection_it),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = connnection_it,
+		.length = ARRAY_SIZE(connnection_it),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+	{
+		.data = proto_error,
+		.length = ARRAY_SIZE(proto_error),
+		.dir = DIR_BSC,
+		.result = 0,
+	},
+	{
+		.data = proto_error,
+		.length = ARRAY_SIZE(proto_error),
+		.dir = DIR_MSC,
+		.result = 0,
+	},
+
+};
+
+static void test_filter(void)
+{
+	int i;
+
+
+	/* start testinh with proper messages */
+	fprintf(stderr, "Testing BSS Filtering.\n");
+	for (i = 0; i < ARRAY_SIZE(results); ++i) {
+		int result;
+		struct bsc_nat_parsed *parsed;
+		struct msgb *msg = msgb_alloc(4096, "test-message");
+
+		fprintf(stderr, "Going to test item: %d\n", i);
+		memcpy(msg->data, results[i].data, results[i].length);
+		msg->l2h = msgb_put(msg, results[i].length);
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed) {
+			fprintf(stderr, "FAIL: Failed to parse the message\n");
+			continue;
+		}
+
+		result = bsc_nat_filter_ipa(results[i].dir, msg, parsed);
+		if (result != results[i].result) {
+			fprintf(stderr, "FAIL: Not the expected result got: %d wanted: %d\n",
+				result, results[i].result);
+		}
+
+		msgb_free(msg);
+	}
+}
+
+#include "bsc_data.c"
+
+static void copy_to_msg(struct msgb *msg, const uint8_t *data, unsigned int length)
+{
+	msgb_reset(msg);
+	msg->l2h = msgb_put(msg, length);
+	memcpy(msg->l2h, data, msgb_l2len(msg));
+}
+
+#define VERIFY(con_found, con, msg, ver, str) \
+	if (!con_found || con_found->bsc != con) { \
+		fprintf(stderr, "Failed to find the con: %p\n", con_found); \
+		abort(); \
+	} \
+	if (memcmp(msg->data, ver, sizeof(ver)) != 0) { \
+		fprintf(stderr, "Failed to patch the %s msg.\n", str); \
+		abort(); \
+	}
+
+/* test conn tracking once */
+static void test_contrack()
+{
+	struct bsc_nat *nat;
+	struct bsc_connection *con;
+	struct sccp_connections *con_found;
+	struct sccp_connections *rc_con;
+	struct bsc_nat_parsed *parsed;
+	struct msgb *msg;
+
+	fprintf(stderr, "Testing connection tracking.\n");
+	nat = bsc_nat_alloc();
+	con = bsc_connection_alloc(nat);
+	con->cfg = bsc_config_alloc(nat, "foo");
+	bsc_config_add_lac(con->cfg, 23);
+	bsc_config_add_lac(con->cfg, 49);
+	bsc_config_add_lac(con->cfg, 42);
+	bsc_config_del_lac(con->cfg, 49);
+	bsc_config_add_lac(con->cfg, 1111);
+	msg = msgb_alloc(4096, "test");
+
+	/* 1.) create a connection */
+	copy_to_msg(msg, bsc_cr, sizeof(bsc_cr));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	if (con_found != NULL) {
+		fprintf(stderr, "Con should not exist %p\n", con_found);
+		abort();
+	}
+	rc_con = create_sccp_src_ref(con, parsed);
+	if (!rc_con) {
+		fprintf(stderr, "Failed to create a ref\n");
+		abort();
+	}
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	if (!con_found || con_found->bsc != con) {
+		fprintf(stderr, "Failed to find the con: %p\n", con_found);
+		abort();
+	}
+	if (con_found != rc_con) {
+		fprintf(stderr, "Failed to find the right connection.\n");
+		abort();
+	}
+	if (memcmp(msg->data, bsc_cr_patched, sizeof(bsc_cr_patched)) != 0) {
+		fprintf(stderr, "Failed to patch the BSC CR msg.\n");
+		abort();
+	}
+	talloc_free(parsed);
+
+	/* 2.) get the cc */
+	copy_to_msg(msg, msc_cc, sizeof(msc_cc));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+	VERIFY(con_found, con, msg, msc_cc_patched, "MSC CC");
+	if (update_sccp_src_ref(con_found, parsed) != 0) {
+		fprintf(stderr, "Failed to update the SCCP con.\n");
+		abort();
+	}
+
+	/* 3.) send some data */
+	copy_to_msg(msg, bsc_dtap, sizeof(bsc_dtap));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	VERIFY(con_found, con, msg, bsc_dtap_patched, "BSC DTAP");
+
+	/* 4.) receive some data */
+	copy_to_msg(msg, msc_dtap, sizeof(msc_dtap));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+	VERIFY(con_found, con, msg, msc_dtap_patched, "MSC DTAP");
+
+	/* 5.) close the connection */
+	copy_to_msg(msg, msc_rlsd, sizeof(msc_rlsd));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+	VERIFY(con_found, con, msg, msc_rlsd_patched, "MSC RLSD");
+
+	/* 6.) confirm the connection close */
+	copy_to_msg(msg, bsc_rlc, sizeof(bsc_rlc));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+	if (!con_found || con_found->bsc != con) {
+		fprintf(stderr, "Failed to find the con: %p\n", con_found);
+		abort();
+	}
+	if (memcmp(msg->data, bsc_rlc_patched, sizeof(bsc_rlc_patched)) != 0) {
+		fprintf(stderr, "Failed to patch the BSC CR msg.\n");
+		abort();
+	}
+	remove_sccp_src_ref(con, msg, parsed);
+	talloc_free(parsed);
+
+	copy_to_msg(msg, bsc_rlc, sizeof(bsc_rlc));
+	parsed = bsc_nat_parse(msg);
+	con_found = patch_sccp_src_ref_to_msc(msg, parsed, con);
+
+	/* verify that it is gone */
+	if (con_found != NULL) {
+		fprintf(stderr, "Con should be gone. %p\n", con_found);
+		abort();
+	}
+	talloc_free(parsed);
+
+
+	bsc_config_free(con->cfg);
+	talloc_free(nat);
+	msgb_free(msg);
+}
+
+static void test_paging(void)
+{
+	int lac;
+	struct bsc_nat *nat;
+	struct bsc_connection *con;
+	struct bsc_nat_parsed *parsed;
+	struct bsc_config *cfg;
+	struct msgb *msg;
+
+	fprintf(stderr, "Testing paging by lac.\n");
+
+	nat = bsc_nat_alloc();
+	con = bsc_connection_alloc(nat);
+	cfg = bsc_config_alloc(nat, "unknown");
+	con->cfg = cfg;
+	bsc_config_add_lac(cfg, 23);
+	con->authenticated = 1;
+	llist_add(&con->list_entry, &nat->bsc_connections);
+	msg = msgb_alloc(4096, "test");
+
+	/* Test completely bad input */
+	copy_to_msg(msg, paging_by_lac_cmd, sizeof(paging_by_lac_cmd));
+	if (bsc_nat_find_bsc(nat, msg, &lac) != 0) {
+		fprintf(stderr, "Should have not found anything.\n");
+		abort();
+	}
+
+	/* Test it by not finding it */
+	copy_to_msg(msg, paging_by_lac_cmd, sizeof(paging_by_lac_cmd));
+	parsed = bsc_nat_parse(msg);
+	if (bsc_nat_find_bsc(nat, msg, &lac) != 0) {
+		fprintf(stderr, "Should have not found aynthing.\n");
+		abort();
+	}
+	talloc_free(parsed);
+
+	/* Test by finding it */
+	bsc_config_del_lac(cfg, 23);
+	bsc_config_add_lac(cfg, 8213);
+	copy_to_msg(msg, paging_by_lac_cmd, sizeof(paging_by_lac_cmd));
+	parsed = bsc_nat_parse(msg);
+	if (bsc_nat_find_bsc(nat, msg, &lac) != con) {
+		fprintf(stderr, "Should have found it.\n");
+		abort();
+	}
+	talloc_free(parsed);
+}
+
+static void test_mgcp_allocations(void)
+{
+#if 0
+	struct bsc_connection *bsc;
+	struct bsc_nat *nat;
+	struct sccp_connections con;
+	int i, j, multiplex;
+
+	fprintf(stderr, "Testing MGCP.\n");
+	memset(&con, 0, sizeof(con));
+
+	nat = bsc_nat_alloc();
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       65);
+	nat->mgcp_cfg = mgcp_config_alloc();
+	nat->mgcp_cfg->trunk.number_endpoints = 64;
+
+	bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo");
+	bsc->cfg->max_endpoints = 60;
+	bsc_config_add_lac(bsc->cfg, 2323);
+	bsc->last_endpoint = 0x22;
+	con.bsc = bsc;
+
+	bsc_init_endps_if_needed(bsc);
+
+	i  = 1;
+	do {
+		if (bsc_assign_endpoint(bsc, &con) != 0) {
+			fprintf(stderr, "failed to allocate... on iteration %d\n", i);
+			break;
+		}
+		++i;
+	} while(1);
+
+	multiplex = bsc_mgcp_nr_multiplexes(bsc->cfg->max_endpoints);
+	for (i = 0; i < multiplex; ++i) {
+		for (j = 0; j < 32; ++j)
+			printf("%d", bsc->_endpoint_status[i*32 + j]);
+		printf(": %d of %d\n", i*32 + 32, 32 * 8);
+	}
+#endif
+}
+
+static void test_mgcp_ass_tracking(void)
+{
+	struct bsc_connection *bsc;
+	struct bsc_nat *nat;
+	struct sccp_connections con;
+	struct bsc_nat_parsed *parsed;
+	struct msgb *msg;
+
+	fprintf(stderr, "Testing MGCP.\n");
+	memset(&con, 0, sizeof(con));
+
+	nat = bsc_nat_alloc();
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       33);
+	nat->mgcp_cfg = mgcp_config_alloc();
+	nat->mgcp_cfg->trunk.number_endpoints = 64;
+
+	bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo");
+	bsc_config_add_lac(bsc->cfg, 2323);
+	bsc->last_endpoint = 0x1e;
+	con.bsc = bsc;
+
+	msg = msgb_alloc(4096, "foo");
+	copy_to_msg(msg, ass_cmd, sizeof(ass_cmd));
+	parsed = bsc_nat_parse(msg);
+
+	if (msg->l2h[16] != 0 ||
+	    msg->l2h[17] != 0x1) {
+		fprintf(stderr, "Input is not as expected.. %s 0x%x\n",
+			hexdump(msg->l2h, msgb_l2len(msg)),
+			msg->l2h[17]);
+		abort();
+	}
+
+	if (bsc_mgcp_assign_patch(&con, msg) != 0) {
+		fprintf(stderr, "Failed to handle assignment.\n");
+		abort();
+	}
+
+	if (con.msc_endp != 1) {
+		fprintf(stderr, "Timeslot should be 1.\n");
+		abort();
+	}
+
+	if (con.bsc_endp != 0x1) {
+		fprintf(stderr, "Assigned timeslot should have been 1.\n");
+		abort();
+	}
+	if (con.bsc->_endpoint_status[0x1] != 1) {
+		fprintf(stderr, "The status on the BSC is wrong.\n");
+		abort();
+	}
+
+	int multiplex, timeslot;
+	mgcp_endpoint_to_timeslot(0x1, &multiplex, &timeslot);
+
+	uint16_t cic = htons(timeslot & 0x1f);
+	if (memcmp(&cic, &msg->l2h[16], sizeof(cic)) != 0) {
+		fprintf(stderr, "Message was not patched properly\n");
+		fprintf(stderr, "data cic: 0x%x %s\n", cic, hexdump(msg->l2h, msgb_l2len(msg)));
+		abort();
+	}
+
+	talloc_free(parsed);
+
+	bsc_mgcp_dlcx(&con);
+	if (con.bsc_endp != -1 || con.msc_endp != -1 ||
+	    con.bsc->_endpoint_status[1] != 0 || con.bsc->last_endpoint != 0x1) {
+		fprintf(stderr, "Clearing should remove the mapping.\n");
+		abort();
+	}
+
+	bsc_config_free(bsc->cfg);
+	talloc_free(nat);
+}
+
+/* test the code to find a given connection */
+static void test_mgcp_find(void)
+{
+	struct bsc_nat *nat;
+	struct bsc_connection *con;
+	struct sccp_connections *sccp_con;
+
+	fprintf(stderr, "Testing finding of a BSC Connection\n");
+
+	nat = bsc_nat_alloc();
+	con = bsc_connection_alloc(nat);
+	llist_add(&con->list_entry, &nat->bsc_connections);
+
+	sccp_con = talloc_zero(con, struct sccp_connections);
+	sccp_con->msc_endp = 12;
+	sccp_con->bsc_endp = 12;
+	sccp_con->bsc = con;
+	llist_add(&sccp_con->list_entry, &nat->sccp_connections);
+
+	if (bsc_mgcp_find_con(nat, 11) != NULL) {
+		fprintf(stderr, "Found the wrong connection.\n");
+		abort();
+	}
+
+	if (bsc_mgcp_find_con(nat, 12) != sccp_con) {
+		fprintf(stderr, "Didn't find the connection\n");
+		abort();
+	}
+
+	/* free everything */
+	talloc_free(nat);
+}
+
+static void test_mgcp_rewrite(void)
+{
+	int i;
+	struct msgb *output;
+	fprintf(stderr, "Test rewriting MGCP messages.\n");
+
+	for (i = 0; i < ARRAY_SIZE(mgcp_messages); ++i) {
+		const char *orig = mgcp_messages[i].orig;
+		const char *patc = mgcp_messages[i].patch;
+		const char *ip = mgcp_messages[i].ip;
+		const int port = mgcp_messages[i].port;
+
+		char *input = strdup(orig);
+
+		output = bsc_mgcp_rewrite(input, strlen(input), 0x1e, ip, port);
+		if (msgb_l2len(output) != strlen(patc)) {
+			fprintf(stderr, "Wrong sizes for test: %d  %d != %d != %d\n", i, msgb_l2len(output), strlen(patc), strlen(orig));
+			fprintf(stderr, "String '%s' vs '%s'\n", (const char *) output->l2h, patc);
+			abort();
+		}
+
+		if (memcmp(output->l2h, patc, msgb_l2len(output)) != 0) {
+			fprintf(stderr, "Broken on %d msg: '%s'\n", i, (const char *) output->l2h);
+			abort();
+		}
+
+		msgb_free(output);
+		free(input);
+	}
+}
+
+static void test_mgcp_parse(void)
+{
+	int code, ci;
+	char transaction[60];
+
+	fprintf(stderr, "Test MGCP response parsing.\n");
+
+	if (bsc_mgcp_parse_response(crcx_resp, &code, transaction) != 0) {
+		fprintf(stderr, "Failed to parse CRCX resp.\n");
+		abort();
+	}
+
+	if (code != 200) {
+		fprintf(stderr, "Failed to parse the CODE properly. Got: %d\n", code);
+		abort();
+	}
+
+	if (strcmp(transaction, "23265295") != 0) {
+		fprintf(stderr, "Failed to parse transaction id: '%s'\n", transaction);
+		abort();
+	}
+
+	ci = bsc_mgcp_extract_ci(crcx_resp);
+	if (ci != 1) {
+		fprintf(stderr, "Failed to parse the CI. Got: %d\n", ci);
+		abort();
+	}
+}
+
+struct cr_filter {
+	const uint8_t *data;
+	int length;
+	int result;
+	int contype;
+
+	const char *bsc_imsi_allow;
+	const char *bsc_imsi_deny;
+	const char *nat_imsi_deny;
+};
+
+static struct cr_filter cr_filter[] = {
+	{
+		.data = bssmap_cr,
+		.length = sizeof(bssmap_cr),
+		.result = 1,
+		.contype = NAT_CON_TYPE_CM_SERV_REQ,
+	},
+	{
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.contype = NAT_CON_TYPE_LU,
+	},
+	{
+		.data = pag_resp,
+		.length = sizeof(pag_resp),
+		.result = 1,
+		.contype = NAT_CON_TYPE_PAG_RESP,
+	},
+	{
+		/* nat deny is before blank/null BSC */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -3,
+		.nat_imsi_deny = "[0-9]*",
+		.contype = NAT_CON_TYPE_LU,
+	},
+	{
+		/* BSC allow is before NAT deny */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.nat_imsi_deny = "[0-9]*",
+		.bsc_imsi_allow = "2440[0-9]*",
+		.contype = NAT_CON_TYPE_LU,
+	},
+	{
+		/* BSC allow is before NAT deny */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.bsc_imsi_allow = "[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = NAT_CON_TYPE_LU,
+	},
+	{
+		/* filter as deny is first */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = 1,
+		.bsc_imsi_deny = "[0-9]*",
+		.bsc_imsi_allow = "[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = NAT_CON_TYPE_LU,
+	},
+	{
+		/* deny by nat rule */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -3,
+		.bsc_imsi_deny = "000[0-9]*",
+		.nat_imsi_deny = "[0-9]*",
+		.contype = NAT_CON_TYPE_LU,
+	},
+	{
+		/* deny by bsc rule */
+		.data = bss_lu,
+		.length = sizeof(bss_lu),
+		.result = -2,
+		.bsc_imsi_deny = "[0-9]*",
+		.contype = NAT_CON_TYPE_LU,
+	},
+
+};
+
+static void test_cr_filter()
+{
+	int i, res, contype;
+	struct msgb *msg = msgb_alloc(4096, "test_cr_filter");
+	struct bsc_nat_parsed *parsed;
+	struct bsc_nat_acc_lst *nat_lst, *bsc_lst;
+	struct bsc_nat_acc_lst_entry *nat_entry, *bsc_entry;
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+	struct bsc_connection *bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo");
+	bsc_config_add_lac(bsc->cfg, 1234);
+	bsc->cfg->acc_lst_name = "bsc";
+	nat->acc_lst_name = "nat";
+
+	nat_lst = bsc_nat_acc_lst_get(nat, "nat");
+	bsc_lst = bsc_nat_acc_lst_get(nat, "bsc");
+
+	bsc_entry = bsc_nat_acc_lst_entry_create(bsc_lst);
+	nat_entry = bsc_nat_acc_lst_entry_create(nat_lst);
+
+	for (i = 0; i < ARRAY_SIZE(cr_filter); ++i) {
+		char *imsi;
+		msgb_reset(msg);
+		copy_to_msg(msg, cr_filter[i].data, cr_filter[i].length);
+
+		nat_lst = bsc_nat_acc_lst_get(nat, "nat");
+		bsc_lst = bsc_nat_acc_lst_get(nat, "bsc");
+
+		bsc_parse_reg(nat_entry, &nat_entry->imsi_deny_re, &nat_entry->imsi_deny,
+			      cr_filter[i].nat_imsi_deny ? 1 : 0,
+			      &cr_filter[i].nat_imsi_deny);
+		bsc_parse_reg(bsc_entry, &bsc_entry->imsi_allow_re, &bsc_entry->imsi_allow,
+			      cr_filter[i].bsc_imsi_allow ? 1 : 0,
+			      &cr_filter[i].bsc_imsi_allow);
+		bsc_parse_reg(bsc_entry, &bsc_entry->imsi_deny_re, &bsc_entry->imsi_deny,
+			      cr_filter[i].bsc_imsi_deny ? 1 : 0,
+			      &cr_filter[i].bsc_imsi_deny);
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed) {
+			fprintf(stderr, "FAIL: Failed to parse the message\n");
+			abort();
+		}
+
+		res = bsc_nat_filter_sccp_cr(bsc, msg, parsed, &contype, &imsi);
+		if (res != cr_filter[i].result) {
+			fprintf(stderr, "FAIL: Wrong result %d for test %d.\n", res, i);
+			abort();
+		}
+
+		if (contype != cr_filter[i].contype) {
+			fprintf(stderr, "FAIL: Wrong contype %d for test %d.\n", res, contype);
+			abort();
+		}
+
+		talloc_steal(parsed, imsi);
+		talloc_free(parsed);
+	}
+
+	msgb_free(msg);
+}
+
+static void test_dt_filter()
+{
+	int i;
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct bsc_nat_parsed *parsed;
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+	struct bsc_connection *bsc = bsc_connection_alloc(nat);
+	struct sccp_connections *con = talloc_zero(0, struct sccp_connections);
+
+	bsc->cfg = bsc_config_alloc(nat, "foo");
+	bsc_config_add_lac(bsc->cfg, 23);
+	con->bsc = bsc;
+
+	msgb_reset(msg);
+	copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp));
+
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		fprintf(stderr, "FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	if (parsed->bssap != BSSAP_MSG_DTAP) {
+		fprintf(stderr, "FAIL: It should be dtap\n");
+		abort();
+	}
+
+	/* gsm_type is actually the size of the dtap */
+	if (parsed->gsm_type < msgb_l3len(msg) - 3) {
+		fprintf(stderr, "FAIL: Not enough space for the content\n");
+		abort();
+	}
+
+	if (bsc_nat_filter_dt(bsc, msg, con, parsed) != 1) {
+		fprintf(stderr, "FAIL: Should have passed..\n");
+		abort();
+	}
+
+	/* just some basic length checking... */
+	for (i = ARRAY_SIZE(id_resp); i >= 0; --i) {
+		msgb_reset(msg);
+		copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp));
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed)
+			continue;
+
+		con->imsi_checked = 0;
+		bsc_nat_filter_dt(bsc, msg, con, parsed);
+	}
+}
+
+static void test_setup_rewrite()
+{
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct msgb *out;
+	struct bsc_nat_parsed *parsed;
+	const char *imsi = "27408000001234";
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+
+	/* a fake list */
+	struct msg_entries entries;
+	struct msg_entry entry;
+
+	INIT_LLIST_HEAD(&entries.entry);
+	entry.mcc = "274";
+	entry.mnc = "08";
+	entry.option = "^0([1-9])";
+	entry.text = "0049";
+	llist_add_tail(&entry.list, &entries.entry);
+	nat->num_rewr = &entries;
+
+	/* verify that nothing changed */
+	msgb_reset(msg);
+	copy_to_msg(msg, cc_setup_international, ARRAY_SIZE(cc_setup_international));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		fprintf(stderr, "FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi);
+	if (msg != out) {
+		fprintf(stderr, "FAIL: The message should not have been changed\n");
+		abort();
+	}
+
+	if (out->len != ARRAY_SIZE(cc_setup_international)) {
+		fprintf(stderr, "FAIL: Length of message changed\n");
+		abort();
+	}
+
+	if (memcmp(out->data, cc_setup_international, out->len) != 0) {
+		fprintf(stderr, "FAIL: Content modified..\n");
+		abort();
+	}
+	talloc_free(parsed);
+
+	/* verify that something in the message changes */
+	msgb_reset(msg);
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		fprintf(stderr, "FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi);
+	if (!out) {
+		fprintf(stderr, "FAIL: A new message should be created.\n");
+		abort();
+	}
+
+	if (msg == out) {
+		fprintf(stderr, "FAIL: The message should have changed\n");
+		abort();
+	}
+
+	if (out->len != ARRAY_SIZE(cc_setup_national_patched)) {
+		fprintf(stderr, "FAIL: Length is wrong.\n");
+		abort();
+	}
+
+	if (memcmp(cc_setup_national_patched, out->data, out->len) != 0) {
+		fprintf(stderr, "FAIL: Data is wrong.\n");
+		fprintf(stderr, "Data was: %s\n", hexdump(out->data, out->len));
+		abort();
+	}
+
+	msgb_free(out);
+
+	/* Make sure that a wildcard is matching */
+	entry.mnc = "*";
+	msg = msgb_alloc(4096, "test_dt_filter");
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		fprintf(stderr, "FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi);
+	if (!out) {
+		fprintf(stderr, "FAIL: A new message should be created.\n");
+		abort();
+	}
+
+	if (msg == out) {
+		fprintf(stderr, "FAIL: The message should have changed\n");
+		abort();
+	}
+
+	if (out->len != ARRAY_SIZE(cc_setup_national_patched)) {
+		fprintf(stderr, "FAIL: Length is wrong.\n");
+		abort();
+	}
+
+	if (memcmp(cc_setup_national_patched, out->data, out->len) != 0) {
+		fprintf(stderr, "FAIL: Data is wrong.\n");
+		fprintf(stderr, "Data was: %s\n", hexdump(out->data, out->len));
+		abort();
+	}
+
+	msgb_free(out);
+
+	/* Make sure that a wildcard is matching */
+	entry.mnc = "09";
+	msg = msgb_alloc(4096, "test_dt_filter");
+	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		fprintf(stderr, "FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	out = bsc_nat_rewrite_setup(nat, msg, parsed, imsi);
+	if (out != msg) {
+		fprintf(stderr, "FAIL: The message should be unchanged.\n");
+		abort();
+	}
+
+	if (out->len != ARRAY_SIZE(cc_setup_national)) {
+		fprintf(stderr, "FAIL: Foo\n");
+		abort();
+	}
+
+	if (memcmp(out->data, cc_setup_national, ARRAY_SIZE(cc_setup_national)) != 0) {
+		fprintf(stderr, "FAIL: The message should really be unchanged.\n");
+		abort();
+	}
+
+	msgb_free(out);
+}
+
+int main(int argc, char **argv)
+{
+	struct log_target *stderr_target;
+
+	sccp_set_log_area(DSCCP);
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	test_filter();
+	test_contrack();
+	test_paging();
+	test_mgcp_ass_tracking();
+	test_mgcp_find();
+	test_mgcp_rewrite();
+	test_mgcp_parse();
+	test_cr_filter();
+	test_dt_filter();
+	test_setup_rewrite();
+	test_mgcp_allocations();
+	return 0;
+}
diff --git a/tests/channel/Makefile.am b/tests/channel/Makefile.am
new file mode 100644
index 0000000..bf709ff
--- /dev/null
+++ b/tests/channel/Makefile.am
@@ -0,0 +1,10 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS)
+
+noinst_PROGRAMS = channel_test
+
+channel_test_SOURCES = channel_test.c
+channel_test_LDADD = -ldl -ldbi $(LIBOSMOCORE_LIBS) \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a
diff --git a/tests/channel/Makefile.in b/tests/channel/Makefile.in
new file mode 100644
index 0000000..de4b45e
--- /dev/null
+++ b/tests/channel/Makefile.in
@@ -0,0 +1,451 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = channel_test$(EXEEXT)
+subdir = tests/channel
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_channel_test_OBJECTS = channel_test.$(OBJEXT)
+channel_test_OBJECTS = $(am_channel_test_OBJECTS)
+am__DEPENDENCIES_1 =
+channel_test_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(channel_test_SOURCES)
+DIST_SOURCES = $(channel_test_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS)
+channel_test_SOURCES = channel_test.c
+channel_test_LDADD = -ldl -ldbi $(LIBOSMOCORE_LIBS) \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/channel/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/channel/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+channel_test$(EXEEXT): $(channel_test_OBJECTS) $(channel_test_DEPENDENCIES) 
+	@rm -f channel_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(channel_test_OBJECTS) $(channel_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channel_test.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstPROGRAMS ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/channel/channel_test.c b/tests/channel/channel_test.c
new file mode 100644
index 0000000..4f3c593
--- /dev/null
+++ b/tests/channel/channel_test.c
@@ -0,0 +1,84 @@
+/*
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <assert.h>
+
+#include <osmocore/select.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+
+/* our handler */
+static int subscr_cb(unsigned int hook, unsigned int event, struct msgb *msg, void *data, void *param)
+{
+	assert(hook == 101);
+	assert(event == 200);
+	assert(msg == (void*)0x1323L);
+	assert(data == (void*)0x4242L);
+	assert(param == (void*)0x2342L);
+	printf("Reached, didn't crash, test passed\n");
+	return 0;
+}
+
+/* mock object for testing, directly invoke the cb... maybe later through the timer */
+void paging_request(struct gsm_bts *bts, struct gsm_subscriber *subscriber, int type, gsm_cbfn *cbfn, void *data)
+{
+	cbfn(101, 200, (void*)0x1323L, (void*)0x4242L, data);
+}
+
+
+int main(int argc, char **argv)
+{
+	struct gsm_network *network;
+	struct gsm_bts *bts;
+
+	printf("Testing the gsm_subscriber chan logic\n");
+
+	/* Create a dummy network */
+	network = gsm_network_init(1, 1, NULL);
+	if (!network)
+		exit(1);
+	bts = gsm_bts_alloc(network, GSM_BTS_TYPE_BS11, 0, 0);
+	bts->location_area_code = 23;
+
+	/* Create a dummy subscriber */
+	struct gsm_subscriber *subscr = subscr_alloc();
+	subscr->lac = 23;
+	subscr->net = network;
+
+	/* Ask for a channel... */
+	subscr_get_channel(subscr, RSL_CHANNEED_TCH_F, subscr_cb, (void*)0x2342L);
+
+	while (1) {
+		bsc_select_main(0);
+	}
+}
+
+void _abis_nm_sendmsg() {}
+void sms_alloc() {}
+void gsm_net_update_ctype(struct gsm_network *network) {}
+void gsm48_secure_channel() {}
+void paging_request_stop() {}
+void vty_out() {}
+
+
+struct tlv_definition nm_att_tlvdef;
+
diff --git a/tests/db/Makefile.am b/tests/db/Makefile.am
new file mode 100644
index 0000000..a4395ae
--- /dev/null
+++ b/tests/db/Makefile.am
@@ -0,0 +1,15 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+noinst_PROGRAMS = db_test
+
+db_test_SOURCES = db_test.c
+db_test_LDADD =	$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmsc/libmsc.a \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libabis/libabis.a \
+		$(top_builddir)/src/libtrau/libtrau.a \
+		$(top_builddir)/src/libcommon/libcommon.a \
+		$(LIBOSMOCORE_LIBS) -ldl -ldbi
+
diff --git a/tests/db/Makefile.in b/tests/db/Makefile.in
new file mode 100644
index 0000000..5fd1244
--- /dev/null
+++ b/tests/db/Makefile.in
@@ -0,0 +1,458 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = db_test$(EXEEXT)
+subdir = tests/db
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_db_test_OBJECTS = db_test.$(OBJEXT)
+db_test_OBJECTS = $(am_db_test_OBJECTS)
+am__DEPENDENCIES_1 =
+db_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libabis/libabis.a \
+	$(top_builddir)/src/libtrau/libtrau.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(db_test_SOURCES)
+DIST_SOURCES = $(db_test_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+db_test_SOURCES = db_test.c
+db_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmsc/libmsc.a \
+		$(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libabis/libabis.a \
+		$(top_builddir)/src/libtrau/libtrau.a \
+		$(top_builddir)/src/libcommon/libcommon.a \
+		$(LIBOSMOCORE_LIBS) -ldl -ldbi
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/db/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/db/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+db_test$(EXEEXT): $(db_test_OBJECTS) $(db_test_DEPENDENCIES) 
+	@rm -f db_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(db_test_OBJECTS) $(db_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db_test.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstPROGRAMS ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/db/db_test.c b/tests/db/db_test.c
new file mode 100644
index 0000000..236dc37
--- /dev/null
+++ b/tests/db/db_test.c
@@ -0,0 +1,105 @@
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define COMPARE(original, copy) \
+	if (original->id != copy->id) \
+		fprintf(stderr, "Ids do not match in %s:%d %llu %llu\n", \
+			__FUNCTION__, __LINE__, original->id, copy->id); \
+	if (original->lac != copy->lac) \
+		fprintf(stderr, "LAC do not match in %s:%d %d %d\n", \
+			__FUNCTION__, __LINE__, original->lac, copy->lac); \
+	if (original->authorized != copy->authorized) \
+		fprintf(stderr, "Authorize do not match in %s:%d %d %d\n", \
+			__FUNCTION__, __LINE__, original->authorized, \
+			copy->authorized); \
+	if (strcmp(original->imsi, copy->imsi) != 0) \
+		fprintf(stderr, "IMSIs do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->imsi, copy->imsi); \
+	if (original->tmsi != copy->tmsi) \
+		fprintf(stderr, "TMSIs do not match in %s:%d '%u' '%u'\n", \
+			__FUNCTION__, __LINE__, original->tmsi, copy->tmsi); \
+	if (strcmp(original->name, copy->name) != 0) \
+		fprintf(stderr, "names do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->name, copy->name); \
+	if (strcmp(original->extension, copy->extension) != 0) \
+		fprintf(stderr, "names do not match in %s:%d '%s' '%s'\n", \
+			__FUNCTION__, __LINE__, original->extension, copy->extension); \
+
+int main() {
+
+	if (db_init("hlr.sqlite3")) {
+		printf("DB: Failed to init database. Please check the option settings.\n");
+		return 1;
+	}	 
+	printf("DB: Database initialized.\n");
+
+	if (db_prepare()) {
+		printf("DB: Failed to prepare database.\n");
+		return 1;
+	}
+	printf("DB: Database prepared.\n");
+
+	struct gsm_subscriber *alice = NULL;
+	struct gsm_subscriber *alice_db;
+
+	char *alice_imsi = "3243245432345";
+	alice = db_create_subscriber(NULL, alice_imsi);
+	db_sync_subscriber(alice);
+	alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice->imsi);
+	COMPARE(alice, alice_db);
+	subscr_put(alice_db);
+	subscr_put(alice);
+
+	alice_imsi = "3693245423445";
+	alice = db_create_subscriber(NULL, alice_imsi);
+	db_subscriber_assoc_imei(alice, "1234567890");
+	db_subscriber_alloc_tmsi(alice);
+	alice->lac=42;
+	db_sync_subscriber(alice);
+	alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice_imsi);
+	COMPARE(alice, alice_db);
+	subscr_put(alice);
+	subscr_put(alice_db);
+
+	alice_imsi = "9993245423445";
+	alice = db_create_subscriber(NULL, alice_imsi);
+	db_subscriber_alloc_tmsi(alice);
+	alice->lac=42;
+	db_sync_subscriber(alice);
+	db_subscriber_assoc_imei(alice, "1234567890");
+	db_subscriber_assoc_imei(alice, "6543560920");
+	alice_db = db_get_subscriber(NULL, GSM_SUBSCRIBER_IMSI, alice_imsi);
+	COMPARE(alice, alice_db);
+	subscr_put(alice);
+	subscr_put(alice_db);
+
+	db_fini();
+
+	return 0;
+}
+
+/* stubs */
+void vty_out() {}
diff --git a/tests/debug/Makefile.am b/tests/debug/Makefile.am
new file mode 100644
index 0000000..5d6b363
--- /dev/null
+++ b/tests/debug/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+noinst_PROGRAMS = debug_test
+
+debug_test_SOURCES = debug_test.c
+debug_test_LDADD = 	$(LIBOSMOCORE_LIBS) \
+			$(top_builddir)/src/libcommon/libcommon.a
diff --git a/tests/debug/Makefile.in b/tests/debug/Makefile.in
new file mode 100644
index 0000000..c87c169
--- /dev/null
+++ b/tests/debug/Makefile.in
@@ -0,0 +1,447 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = debug_test$(EXEEXT)
+subdir = tests/debug
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_debug_test_OBJECTS = debug_test.$(OBJEXT)
+debug_test_OBJECTS = $(am_debug_test_OBJECTS)
+am__DEPENDENCIES_1 =
+debug_test_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+	$(top_builddir)/src/libcommon/libcommon.a
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(debug_test_SOURCES)
+DIST_SOURCES = $(debug_test_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS)
+debug_test_SOURCES = debug_test.c
+debug_test_LDADD = $(LIBOSMOCORE_LIBS) \
+			$(top_builddir)/src/libcommon/libcommon.a
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/debug/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/debug/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+debug_test$(EXEEXT): $(debug_test_OBJECTS) $(debug_test_DEPENDENCIES) 
+	@rm -f debug_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(debug_test_OBJECTS) $(debug_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug_test.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstPROGRAMS ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/debug/debug_test.c b/tests/debug/debug_test.c
new file mode 100644
index 0000000..6800021
--- /dev/null
+++ b/tests/debug/debug_test.c
@@ -0,0 +1,42 @@
+/* simple test for the debug interface */
+/*
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+
+
+int main(int argc, char **argv)
+{
+	struct log_target *stderr_target;
+
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	log_parse_category_mask(stderr_target, "DRLL");
+	DEBUGP(DCC, "You should not see this\n");
+
+	log_parse_category_mask(stderr_target, "DRLL:DCC");
+	DEBUGP(DRLL, "You should see this\n");
+	DEBUGP(DCC, "You should see this\n");
+	DEBUGP(DMM, "You should not see this\n");
+
+	return 0;
+}
diff --git a/tests/gsm0408/Makefile.am b/tests/gsm0408/Makefile.am
new file mode 100644
index 0000000..de6feb2
--- /dev/null
+++ b/tests/gsm0408/Makefile.am
@@ -0,0 +1,9 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+noinst_PROGRAMS = gsm0408_test
+
+gsm0408_test_SOURCES = gsm0408_test.c
+gsm0408_test_LDADD =	$(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libmsc/libmsc.a \
+			$(top_builddir)/src/libbsc/libbsc.a \
+			$(LIBOSMOCORE_LIBS) -ldbi
diff --git a/tests/gsm0408/Makefile.in b/tests/gsm0408/Makefile.in
new file mode 100644
index 0000000..dc7d625
--- /dev/null
+++ b/tests/gsm0408/Makefile.in
@@ -0,0 +1,450 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = gsm0408_test$(EXEEXT)
+subdir = tests/gsm0408
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_gsm0408_test_OBJECTS = gsm0408_test.$(OBJEXT)
+gsm0408_test_OBJECTS = $(am_gsm0408_test_OBJECTS)
+am__DEPENDENCIES_1 =
+gsm0408_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmsc/libmsc.a \
+	$(top_builddir)/src/libbsc/libbsc.a $(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(gsm0408_test_SOURCES)
+DIST_SOURCES = $(gsm0408_test_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS)
+gsm0408_test_SOURCES = gsm0408_test.c
+gsm0408_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+			$(top_builddir)/src/libmsc/libmsc.a \
+			$(top_builddir)/src/libbsc/libbsc.a \
+			$(LIBOSMOCORE_LIBS) -ldbi
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/gsm0408/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/gsm0408/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+gsm0408_test$(EXEEXT): $(gsm0408_test_OBJECTS) $(gsm0408_test_DEPENDENCIES) 
+	@rm -f gsm0408_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(gsm0408_test_OBJECTS) $(gsm0408_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm0408_test.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstPROGRAMS ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
new file mode 100644
index 0000000..e8998c3
--- /dev/null
+++ b/tests/gsm0408/gsm0408_test.c
@@ -0,0 +1,103 @@
+/* simple test for the gsm0408 formatting functions */
+/*
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+#define COMPARE(result, op, value) \
+    if (!((result) op (value))) {\
+	fprintf(stderr, "Compare failed. Was %x should be %x in %s:%d\n",result, value, __FILE__, __LINE__); \
+	exit(-1); \
+    }
+
+#define COMPARE_STR(result, value) \
+	if (strcmp(result, value) != 0) { \
+		fprintf(stderr, "Compare failed. Was %s should be %s in %s:%d\n",result, value, __FILE__, __LINE__); \
+		exit(-1); \
+	}
+
+/*
+ * Test Location Area Identifier formatting. Table 10.5.3 of 04.08
+ */
+static void test_location_area_identifier(void)
+{
+    struct gsm48_loc_area_id lai48;
+
+    printf("Testing test location area identifier\n");
+
+    /*
+     * Test the default/test setup. Coming from
+     * bsc_hack.c dumps
+     */
+    gsm48_generate_lai(&lai48, 1, 1, 1);
+    COMPARE(lai48.digits[0], ==, 0x00);
+    COMPARE(lai48.digits[1], ==, 0xF1);
+    COMPARE(lai48.digits[2], ==, 0x10);
+    COMPARE(lai48.lac, ==, htons(0x0001));
+
+    gsm48_generate_lai(&lai48, 602, 1, 15);
+    COMPARE(lai48.digits[0], ==, 0x06);
+    COMPARE(lai48.digits[1], ==, 0xF2);
+    COMPARE(lai48.digits[2], ==, 0x10);
+    COMPARE(lai48.lac, ==, htons(0x000f));
+}
+
+static void test_mi_functionality(void)
+{
+	const char *imsi_odd  = "987654321098763";
+	const char *imsi_even = "9876543210987654";
+	const u_int32_t tmsi = 0xfabeacd0;
+	u_int8_t mi[128];
+	unsigned int mi_len;
+	char mi_parsed[GSM48_MI_SIZE];
+
+	printf("Testing parsing and generating TMSI/IMSI\n");
+
+	/* tmsi code */
+	mi_len = gsm48_generate_mid_from_tmsi(mi, tmsi);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len - 2);
+	COMPARE((u_int32_t)strtoul(mi_parsed, NULL, 10), ==, tmsi);
+
+	/* imsi code */
+	mi_len = gsm48_generate_mid_from_imsi(mi, imsi_odd);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+	printf("hex: %s\n", hexdump(mi, mi_len));
+	COMPARE_STR(mi_parsed, imsi_odd);
+
+	mi_len = gsm48_generate_mid_from_imsi(mi, imsi_even);
+	gsm48_mi_to_string(mi_parsed, sizeof(mi_parsed), mi + 2, mi_len -2);
+	printf("hex: %s\n", hexdump(mi, mi_len));
+	COMPARE_STR(mi_parsed, imsi_even);
+}
+
+int main(int argc, char **argv)
+{
+	test_location_area_identifier();
+	test_mi_functionality();
+
+	exit(0);
+}
diff --git a/tests/mgcp/Makefile.am b/tests/mgcp/Makefile.am
new file mode 100644
index 0000000..472368c
--- /dev/null
+++ b/tests/mgcp/Makefile.am
@@ -0,0 +1,12 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+noinst_PROGRAMS = mgcp_test
+
+mgcp_test_SOURCES = mgcp_test.c
+
+mgcp_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmgcp/libmgcp.a \
+		$(top_builddir)/src/libcommon/libcommon.a \
+		$(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS)
diff --git a/tests/mgcp/Makefile.in b/tests/mgcp/Makefile.in
new file mode 100644
index 0000000..a8df766
--- /dev/null
+++ b/tests/mgcp/Makefile.in
@@ -0,0 +1,453 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = mgcp_test$(EXEEXT)
+subdir = tests/mgcp
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_mgcp_test_OBJECTS = mgcp_test.$(OBJEXT)
+mgcp_test_OBJECTS = $(am_mgcp_test_OBJECTS)
+am__DEPENDENCIES_1 =
+mgcp_test_DEPENDENCIES = $(top_builddir)/src/libbsc/libbsc.a \
+	$(top_builddir)/src/libmgcp/libmgcp.a \
+	$(top_builddir)/src/libcommon/libcommon.a \
+	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(mgcp_test_SOURCES)
+DIST_SOURCES = $(mgcp_test_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+mgcp_test_SOURCES = mgcp_test.c
+mgcp_test_LDADD = $(top_builddir)/src/libbsc/libbsc.a \
+		$(top_builddir)/src/libmgcp/libmgcp.a \
+		$(top_builddir)/src/libcommon/libcommon.a \
+		$(LIBOSMOCORE_LIBS) -lrt $(LIBOSMOSCCP_LIBS) $(LIBOSMOVTY_LIBS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/mgcp/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu tests/mgcp/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+	-test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+mgcp_test$(EXEEXT): $(mgcp_test_OBJECTS) $(mgcp_test_DEPENDENCIES) 
+	@rm -f mgcp_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(mgcp_test_OBJECTS) $(mgcp_test_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mgcp_test.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstPROGRAMS ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c
new file mode 100644
index 0000000..4052377
--- /dev/null
+++ b/tests/mgcp/mgcp_test.c
@@ -0,0 +1,85 @@
+/*
+ * (C) 2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+
+#include <osmocore/talloc.h>
+#include <string.h>
+
+static struct msgb *create_auep1()
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+	int len = sprintf((char *)msg->data, "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n");
+	msg->l2h = msgb_put(msg, len);
+	return msg;
+}
+
+static struct msgb *create_auep2()
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(4096, 128, "MGCP msg");
+	int len = sprintf((char *)msg->data, "AUEP 18983213 ds/e1-2/1@172.16.6.66 MGCP 1.0\r\n");
+	msg->l2h = msgb_put(msg, len);
+	return msg;
+}
+
+static void test_auep(void)
+{
+	struct msgb *inp;
+	struct msgb *msg;
+	struct mgcp_config *cfg = mgcp_config_alloc();
+	cfg->trunk.number_endpoints = 64;
+	mgcp_endpoints_allocate(&cfg->trunk);
+
+	mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1));
+
+	inp = create_auep1();
+	msg = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	if (strcmp((char *) msg->data, "200 158663169 OK\r\n") != 0)
+		printf("Result1 failed '%s'\n", (char *) msg->data);
+	/* Verify that the endpoint is fine */
+	msgb_free(msg);
+
+	inp = create_auep2();
+	msg = mgcp_handle_message(cfg, inp);
+	msgb_free(inp);
+	/* Verify that the endpoint is not fine */
+	if (strcmp((char *) msg->data, "500 18983213 FAIL\r\n") != 0)
+		printf("Result2 failed '%s'\n", (char *) msg->data);
+	msgb_free(msg);
+
+	talloc_free(cfg);
+}
+
+int main(int argc, char **argv)
+{
+	struct log_target *stderr_target;
+	log_init(&log_info);
+	stderr_target = log_target_create_stderr();
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+
+	test_auep();
+	return 0;
+}