-
Notifications
You must be signed in to change notification settings - Fork 36
elixir: Initial commit #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Munksgaard
wants to merge
2
commits into
tailscale:main
Choose a base branch
from
Munksgaard:elixir
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Used by "mix format" | ||
| [ | ||
| inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # The directory Mix will write compiled artifacts to. | ||
| /_build/ | ||
|
|
||
| # If you run "mix test --cover", coverage assets end up here. | ||
| /cover/ | ||
|
|
||
| # The directory Mix downloads your dependencies sources to. | ||
| /deps/ | ||
|
|
||
| # Where third-party dependencies like ExDoc output generated docs. | ||
| /doc/ | ||
|
|
||
| # Ignore .fetch files in case you like to edit your project deps locally. | ||
| /.fetch | ||
|
|
||
| # If the VM crashes, it generates a dump, let's ignore it too. | ||
| erl_crash.dump | ||
|
|
||
| # Also ignore archive artifacts (built via "mix archive.build"). | ||
| *.ez | ||
|
|
||
| # Ignore package tarball (built via "mix hex.build"). | ||
| ex_tailscale-*.tar | ||
|
|
||
| # Temporary files, for example, from tests. | ||
| /tmp/ | ||
|
|
||
| # Object files | ||
| *.o |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Changelog | ||
|
|
||
| ## Libtailscale v0.1.0 | ||
|
|
||
| The initial release of `Libtailscale`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Libtailscale | ||
|
|
||
| Thin NIF-wrapper around | ||
| [libtailscale](https://github.com/tailscale/libtailscale/). | ||
|
|
||
| > #### Warning {: .warning} | ||
| > | ||
| > Should not be used directly. Use | ||
| > [`gen_tailscale`](https://hex.pm/packages/gen_tailscale) or | ||
| > [`TailscaleTransport`](https://hex.pm/packages/tailscale_transport) instead. | ||
|
|
||
|
|
||
| ## Dependencies | ||
|
|
||
| Building this package requires access to a Go compiler as well as GCC. | ||
|
|
||
| ## Usage | ||
|
|
||
| There's one working example in `examples/echo.exs`. To run, first run `mix | ||
| deps.get` and then `mix run examples/echo.exs`. You will need a | ||
| [Tailscale](https://tailscale.com/) account. The first time you run the example, | ||
| it will ask you to log in by following a link. Alternatively, here is the | ||
| simplest possible echo server: | ||
|
|
||
| ```elixir | ||
| # Create a new Tailscale server object. | ||
| ts = Libtailscale.new() | ||
|
|
||
| # Set the Tailscale connection to be ephemeral. | ||
| :ok = Libtailscale.set_ephemeral(ts, 1) | ||
| :ok = Libtailscale.set_hostname(ts, "libtailscale-echo") | ||
|
|
||
| :ok = Libtailscale.up(ts) | ||
|
|
||
| # Create a listener socket using the NIF. | ||
| {:ok, listener_fd} = Libtailscale.listen(ts, "tcp", ":1999") | ||
|
|
||
| {:ok, listener_socket} = :socket.open(listener_fd) | ||
|
|
||
| # Customer "accept" functionality | ||
| {:ok, cmsg} = :socket.recvmsg(listener_socket) | ||
| <<socket_fd::integer-native-32>> = hd(cmsg.ctrl).data | ||
|
|
||
| {:ok, socket} = :socket.open(socket_fd) | ||
|
|
||
| # Now echo one message | ||
| {:ok, s} = :socket.recv(socket) | ||
| :ok = :socket.send(socket, s) | ||
|
|
||
| # And clean up | ||
| :socket.shutdown(socket, :read_write) | ||
| :socket.close(socket) | ||
| :socket.close(listener_socket) | ||
| ``` | ||
|
|
||
| After running the server (wait for the "state is Running" message), simply use | ||
| `telnet libtailscale-echo 1999` in another terminal to connect to it over the | ||
| tailnet. The server that's running is a simple echo server that will wait for a | ||
| single line of input, return it to the client and then close the connection. | ||
|
|
||
| ## Warning | ||
|
|
||
| This Elixir library (which is also published to | ||
| [Hex](https://hex.pm/packages/libtailscale), the Elixir package manager), but it | ||
| should probably not be used directly. Instead, the | ||
| [`gen_tailscale`](https://hex.pm/packages/gen_tailscale) library should be used, | ||
| which wraps the libtailscale sockets in a `gen_tcp`-like interface. There's also | ||
| also [`tailscale_transport`](https://hex.pm/packages/tailscale_transport), which | ||
| allows users to expose their bandit/phoenix-based app directly to their tailnet | ||
| using `libtailscale` and the `gen_tailscale` wrapper. | ||
|
|
||
| Everything in this chain of packages should be considered proof of concept at | ||
| this point and should not be used for anything important. Especially the | ||
| `gen_tailscale` library has been constructed by crudely hacking the original | ||
| `gen_tcp` module to use `libtailscale` and could use a total rewrite at some | ||
| point. However, it works well enough that my example application | ||
| [`tschat`](https://github.com/Munksgaard/tschat) is able to accept connections | ||
| from different Tailscale users and show their username by retrieving data from | ||
| the Tailscale connection. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Create a new Tailscale server object. | ||
| ts = Libtailscale.new() | ||
|
|
||
| # Set the Tailscale connection to be ephemeral. | ||
| :ok = Libtailscale.set_ephemeral(ts, 1) | ||
| :ok = Libtailscale.set_hostname(ts, "libtailscale-echo") | ||
|
|
||
| :ok = Libtailscale.up(ts) | ||
|
|
||
| {:ok, ips} = Libtailscale.getips(ts) | ||
| IO.puts("Server IPs: #{ips}") | ||
|
|
||
| # Create a listener socket using the NIF. | ||
| {:ok, listener_fd} = Libtailscale.listen(ts, "tcp", ":1999") | ||
|
|
||
| {:ok, listener_socket} = :socket.open(listener_fd) | ||
|
|
||
| # Customer "accept" functionality | ||
| {:ok, cmsg} = :socket.recvmsg(listener_socket) | ||
| <<socket_fd::integer-native-32>> = hd(cmsg.ctrl).data | ||
|
|
||
| {:ok, remoteaddr} = Libtailscale.getremoteaddr(ts, listener_fd, socket_fd) | ||
| IO.puts("Client IP: #{remoteaddr}") | ||
|
|
||
| {:ok, socket} = :socket.open(socket_fd) | ||
|
|
||
| # Now echo one message | ||
| {:ok, s} = :socket.recv(socket) | ||
|
|
||
| :ok = :socket.send(socket, s) | ||
|
|
||
| # And clean up | ||
| :socket.shutdown(socket, :read_write) | ||
|
|
||
| :socket.close(socket) | ||
|
|
||
| :socket.close(listener_socket) | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; | ||
|
|
||
| outputs = inputs: | ||
| let | ||
| supportedSystems = | ||
| [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; | ||
| forEachSupportedSystem = f: | ||
| inputs.nixpkgs.lib.genAttrs supportedSystems | ||
| (system: f { pkgs = import inputs.nixpkgs { inherit system; }; }); | ||
|
|
||
| in { | ||
| devShells = forEachSupportedSystem ({ pkgs }: { | ||
| default = pkgs.mkShell { | ||
| packages = | ||
| [ pkgs.elixir pkgs.go pkgs.gnumake pkgs.gcc ]; | ||
| }; | ||
| }); | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| defmodule Libtailscale do | ||
| @on_load :init | ||
|
|
||
| @appname :libtailscale | ||
| @libname "libtailscale" | ||
|
|
||
| def init do | ||
| so_name = | ||
| case :code.priv_dir(@appname) do | ||
| {:error, :bad_name} -> | ||
| case File.dir?(Path.join(["..", :priv])) do | ||
| true -> | ||
| Path.join(["..", :priv, @libname]) | ||
|
|
||
| _ -> | ||
| Path.join([:priv, @libname]) | ||
| end | ||
|
|
||
| dir -> | ||
| Path.join(dir, @libname) | ||
| end | ||
|
|
||
| :erlang.load_nif(so_name, 0) | ||
| end | ||
|
|
||
| def new() do | ||
| not_loaded(:new) | ||
| end | ||
|
|
||
| def start(_ts) do | ||
| not_loaded(:start) | ||
| end | ||
|
|
||
| def up(_ts) do | ||
| not_loaded(:up) | ||
| end | ||
|
|
||
| def close(_ts) do | ||
| not_loaded(:close) | ||
| end | ||
|
|
||
| def set_dir(_ts, _dir) do | ||
| not_loaded(:set_dir) | ||
| end | ||
|
|
||
| def set_hostname(_ts, _hostname) do | ||
| not_loaded(:set_hostname) | ||
| end | ||
|
|
||
| def set_authkey(_ts, _authkey) do | ||
| not_loaded(:set_authkey) | ||
| end | ||
|
|
||
| def set_control_url(_ts, _control_url) do | ||
| not_loaded(:set_control_url) | ||
| end | ||
|
|
||
| def set_ephemeral(_ts, _ephemeral) do | ||
| not_loaded(:set_ephemeral) | ||
| end | ||
|
|
||
| def set_logfd(_ts, _logfd) do | ||
| not_loaded(:set_logfd) | ||
| end | ||
|
|
||
| def getips(_ts) do | ||
| not_loaded(:getips) | ||
| end | ||
|
|
||
| def dial(_ts, _network, _addr) do | ||
| not_loaded(:dial) | ||
| end | ||
|
|
||
| def listen(_ts, _network, _addr) do | ||
| not_loaded(:listen) | ||
| end | ||
|
|
||
| def getremoteaddr(_ts, _listener, _conn) do | ||
| not_loaded(:getremoteaddr) | ||
| end | ||
|
|
||
| def accept(_ts, _listener) do | ||
| not_loaded(:accept) | ||
| end | ||
|
|
||
| def read(_ts, _conn, _len) do | ||
| not_loaded(:read) | ||
| end | ||
|
|
||
| def write(_ts, _conn, _bin) do | ||
| not_loaded(:write) | ||
| end | ||
|
|
||
| def close_connection(_ts, _conn) do | ||
| not_loaded(:close_connection) | ||
| end | ||
|
|
||
| def close_listener(_ts, _conn) do | ||
| not_loaded(:close_listener) | ||
| end | ||
|
|
||
| def loopback(_ts) do | ||
| not_loaded(:loopback) | ||
| end | ||
|
|
||
| defp not_loaded(line) do | ||
| :erlang.nif_error({:not_loaded, [{:module, __MODULE__}, {:function, line}]}) | ||
| end | ||
| end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In elixir/examples/echo.exs, there's no error handling for network operations. Consider adding proper error handling for socket operations like :socket.recv and :socket.send, especially since this is meant to be an educational example.