• Nu chevron_right

      strcpy: a niche function you don't need

      pubsub.kikeriki.at / null_program · Friday, 30 July, 2021 - 19:37 · 32 minutes

    <p>The C <a href="https://man7.org/linux/man-pages/man3/strcpy.3.html"><code class="language-plaintext highlighter-rouge">strcpy</code></a> function is a common sight in typical C programs. It’s also a source of buffer overflow defects, so linters and code reviewers commonly recommend alternatives such as <a href="https://man7.org/linux/man-pages/man3/strncpy.3.html"><code class="language-plaintext highlighter-rouge">strncpy</code></a> (difficult to use correctly; mismatched semantics), <a href="https://man.openbsd.org/strlcpy.3"><code class="language-plaintext highlighter-rouge">strlcpy</code></a> (non-standard), or C11’s optional <code class="language-plaintext highlighter-rouge">strcpy_s</code> (<a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm">no correct or practical implementations</a>). Besides their individual shortcomings, these answers are incorrect. <code class="language-plaintext highlighter-rouge">strcpy</code> and friends are, at best, incredibly niche, and the correct replacement is <code class="language-plaintext highlighter-rouge">memcpy</code>.</p> <p>If <code class="language-plaintext highlighter-rouge">strcpy</code> is not easily replaced with <code class="language-plaintext highlighter-rouge">memcpy</code> then the code is fundamentally wrong. Either it’s not using <code class="language-plaintext highlighter-rouge">strcpy</code> correctly or it’s doing something dumb and should be rewritten. Highlighting such problems is part of what makes <code class="language-plaintext highlighter-rouge">memcpy</code> such an effective replacement.</p> <p>Note: Everything here applies just as much to <a href="https://man7.org/linux/man-pages/man3/strcat.3.html"><code class="language-plaintext highlighter-rouge">strcat</code></a> and friends.</p> <p>Clarification update: This article is about correctness (objective), not safety (subjective). If the word “safety” comes to mind then you’ve missed the point.</p> <h3 id="common-cases">Common cases</h3> <p>Buffer overflows arise when the destination is smaller than the source. Safe use of <code class="language-plaintext highlighter-rouge">strcpy</code> requires <em>a priori</em> knowledge of the length of the source string length. Usually this knowledge is the exact source string length. If so, <code class="language-plaintext highlighter-rouge">memcpy</code> is not only a trivial substitute, it’s faster since it will not simultaneously search for a null terminator.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span> <span class="o">*</span><span class="nf">my_strdup</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span> <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="kt">char</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">len</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="n">strcpy</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span> <span class="c1">// BAD</span> <span class="p">}</span> <span class="k">return</span> <span class="n">c</span><span class="p">;</span> <span class="p">}</span> <span class="kt">char</span> <span class="o">*</span><span class="nf">my_strdup_v2</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">)</span> <span class="p">{</span> <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="kt">char</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">len</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="n">memcpy</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span> <span class="c1">// GOOD</span> <span class="p">}</span> <span class="k">return</span> <span class="n">c</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>A more benign case is a static source string, i.e. trusted input.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">err</span> <span class="p">{</span> <span class="kt">char</span> <span class="n">message</span><span class="p">[</span><span class="mi">16</span><span class="p">];</span> <span class="p">};</span> <span class="kt">void</span> <span class="nf">set_oom</span><span class="p">(</span><span class="k">struct</span> <span class="n">err</span> <span class="o">*</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> <span class="n">strcpy</span><span class="p">(</span><span class="n">err</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="s">"out of memory"</span><span class="p">);</span> <span class="c1">// BAD</span> <span class="p">}</span> </code></pre></div></div> <p>The size is a compile time constant, so exploit it as such! Even more, a static assertion (C11) can catch mistakes at compile time rather than run time.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">set_oom_v2</span><span class="p">(</span><span class="k">struct</span> <span class="n">err</span> <span class="o">*</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">oom</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"out of memory"</span><span class="p">;</span> <span class="n">static_assert</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">err</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">oom</span><span class="p">));</span> <span class="n">memcpy</span><span class="p">(</span><span class="n">err</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="n">oom</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">oom</span><span class="p">));</span> <span class="p">}</span> <span class="c1">// Or using a macro:</span> <span class="kt">void</span> <span class="nf">set_oom_v3</span><span class="p">(</span><span class="k">struct</span> <span class="n">err</span> <span class="o">*</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> <span class="cp">#define OOM "out of memory" </span> <span class="n">static_assert</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">err</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">OOM</span><span class="p">));</span> <span class="n">memcpy</span><span class="p">(</span><span class="n">err</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="n">OOM</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">OOM</span><span class="p">));</span> <span class="p">}</span> <span class="c1">// Or assignment (implicit memcpy):</span> <span class="kt">void</span> <span class="nf">set_oom_v4</span><span class="p">(</span><span class="k">struct</span> <span class="n">err</span> <span class="o">*</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> <span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">err</span> <span class="n">oom</span> <span class="o">=</span> <span class="p">{</span><span class="s">"out of memory"</span><span class="p">};</span> <span class="o">*</span><span class="n">err</span> <span class="o">=</span> <span class="n">oom</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>This covers the vast majority of cases of already-correct <code class="language-plaintext highlighter-rouge">strcpy</code>.</p> <h3 id="less-common-cases">Less common cases</h3> <p><code class="language-plaintext highlighter-rouge">strcpy</code> can still be correct without knowing the exact source string length. It is enough to know its <em>upper bound</em> does not exceed the destination length. In this example — assuming the input is guaranteed to be null-terminated — this <code class="language-plaintext highlighter-rouge">strcpy</code> is correct without ever knowing the source string length:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">reply</span> <span class="p">{</span> <span class="kt">char</span> <span class="n">message</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span> <span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">;</span> <span class="p">};</span> <span class="k">struct</span> <span class="n">log</span> <span class="p">{</span> <span class="kt">time_t</span> <span class="n">timestamp</span><span class="p">;</span> <span class="kt">char</span> <span class="n">message</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span> <span class="p">};</span> <span class="kt">void</span> <span class="nf">log_reply</span><span class="p">(</span><span class="k">struct</span> <span class="n">log</span> <span class="o">*</span><span class="n">e</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">reply</span> <span class="o">*</span><span class="n">r</span><span class="p">)</span> <span class="p">{</span> <span class="n">e</span><span class="o">-&gt;</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">time</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="n">strcpy</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="n">r</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>This is a rare case where <code class="language-plaintext highlighter-rouge">strncpy</code> has the right semantics. It zeros out unused destination bytes, destroying any previous contents.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">strncpy</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="n">r</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">));</span> <span class="c1">// In this case, same as:</span> <span class="n">memset</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">));</span> <span class="n">strcpy</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="n">r</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">);</span> </code></pre></div></div> <p>It’s not a general <code class="language-plaintext highlighter-rouge">strcpy</code> replacement because <code class="language-plaintext highlighter-rouge">strncpy</code> might not write a null terminator. If the source string does not null-terminate within the destination length, then neither will destination string.</p> <p>As before, we can do better with <code class="language-plaintext highlighter-rouge">memcpy</code>!</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">static_assert</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">r</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">));</span> <span class="n">memcpy</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="n">r</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">r</span><span class="o">-&gt;</span><span class="n">message</span><span class="p">));</span> </code></pre></div></div> <p>This unconditionally copies 32 bytes. But doesn’t it waste time copying bytes it won’t need? No! On modern hardware it’s far better to copy a large, fixed number of bytes than a small, variable number of bytes. After all, <a href="/blog/2017/10/06/">branching is expensive</a>. Searching for and handling that null terminator has a cost. This fixed-size copy is literally two instructions on x86-64 (output of <code class="language-plaintext highlighter-rouge">clang -march=x86-64-v3 -O3</code>):</p> <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">vmovups</span> <span class="nv">ymm0</span><span class="p">,</span> <span class="p">[</span><span class="nb">rsi</span><span class="p">]</span> <span class="nf">vmovups</span> <span class="p">[</span><span class="nb">rdi</span> <span class="o">+</span> <span class="mi">8</span><span class="p">],</span> <span class="nv">ymm0</span> </code></pre></div></div> <p>It’s faster and there’s no <code class="language-plaintext highlighter-rouge">strcpy</code> to attract complaints.</p> <h3 id="niche-cases">Niche cases</h3> <p>So where <em>is</em> <code class="language-plaintext highlighter-rouge">strcpy</code> useful? Only where all of the following apply:</p> <ol> <li> <p>You only know the upper bound of the source string.</p> </li> <li> <p>It’s undesirable to read beyond that length. Maybe storage is limited to the exact length of the string, or the upper bound is very large so an unconditional copy is too expensive.</p> </li> <li> <p>The source string is so long, and the function so hot, that it’s worth avoiding two passes: <code class="language-plaintext highlighter-rouge">strlen</code> followed by <code class="language-plaintext highlighter-rouge">memcpy</code>.</p> </li> </ol> <p>These circumstances are very unusual which makes <code class="language-plaintext highlighter-rouge">strcpy</code> a niche function you probably don’t need. This is the best case I can imagine, and it’s pretty dumb:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">doc</span> <span class="p">{</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">id</span><span class="p">;</span> <span class="kt">char</span> <span class="n">body</span><span class="p">[</span><span class="mi">1L</span><span class="o">&lt;&lt;</span><span class="mi">20</span><span class="p">];</span> <span class="p">};</span> <span class="c1">// Create a new document from a buffer.</span> <span class="c1">//</span> <span class="c1">// If body is more than 1MiB, the behavior is undefined.</span> <span class="k">struct</span> <span class="n">doc</span> <span class="o">*</span><span class="nf">doc_create</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">body</span><span class="p">)</span> <span class="p">{</span> <span class="k">struct</span> <span class="n">doc</span> <span class="o">*</span><span class="n">c</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">c</span><span class="p">));</span> <span class="k">if</span> <span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="n">c</span><span class="o">-&gt;</span><span class="n">id</span> <span class="o">=</span> <span class="n">id_gen</span><span class="p">();</span> <span class="n">assert</span><span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">body</span><span class="p">)</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">c</span><span class="o">-&gt;</span><span class="n">body</span><span class="p">));</span> <span class="n">strcpy</span><span class="p">(</span><span class="n">c</span><span class="o">-&gt;</span><span class="n">body</span><span class="p">,</span> <span class="n">body</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">c</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>If you’re dealing with such large null-terminated strings that (2) and (3) apply then you’re already doing something fundamentally wrong and self-contradictory. The pointer and length should be <a href="/blog/2019/06/30/">kept and passed together</a>. It’s especially essential for a hot function.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">doc_v2</span> <span class="p">{</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">id</span><span class="p">;</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">;</span> <span class="kt">char</span> <span class="n">body</span><span class="p">[];</span> <span class="p">};</span> </code></pre></div></div> <h3 id="bonus-_s-isnt-helping-you">Bonus: <code class="language-plaintext highlighter-rouge">*_s</code> isn’t helping you</h3> <p>C11 introduced “safe” string functions as an optional “Annex K”, each named with a <code class="language-plaintext highlighter-rouge">_s</code> suffix to its “unsafe” counterpart. Here is the prototype for <code class="language-plaintext highlighter-rouge">strcpy_s</code>:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">errno_t</span> <span class="nf">strcpy_s</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="kr">restrict</span> <span class="n">s1</span><span class="p">,</span> <span class="n">rsize_t</span> <span class="n">s1max</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="kr">restrict</span> <span class="n">s2</span><span class="p">);</span> </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">rsize_t</code> is a <code class="language-plaintext highlighter-rouge">size_t</code> with a “restricted” range (<code class="language-plaintext highlighter-rouge">RSIZE_MAX</code>, probably <code class="language-plaintext highlighter-rouge">SIZE_MAX/2</code>) intended to catch integer underflows. If you <a href="/blog/2017/07/19/">accidentally compute a negative length</a>, it will be a very large number in unsigned form. (An indicator that <code class="language-plaintext highlighter-rouge">size_t</code> should have originally been defined as signed.) This will be outside the restricted range, and so the operation isn’t attempted due to a likely underflow.</p> <p>These “safe” functions were modeled after functions of the same name in MSVC. However, as noted, there are no practical implementations of Annex K. The functions in MSVC have different semantics and behavior, and they do not attempt to implement the standard.</p> <p>Worse, they don’t even do what’s promised in <a href="https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strcpy-s-wcscpy-s-mbscpy-s?view=msvc-160">their documentation</a>. The following program should cause a runtime-constraint violation since <code class="language-plaintext highlighter-rouge">-1</code> is an invalid <code class="language-plaintext highlighter-rouge">rsize_t</code> in any reasonable implementation:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define __STDC_WANT_LIB_EXT1__ 1 #include &lt;stdio.h&gt; #include &lt;string.h&gt; </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span> <span class="n">errno_t</span> <span class="n">r</span> <span class="o">=</span> <span class="n">strcpy_s</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="s">"hello"</span><span class="p">);</span> <span class="n">printf</span><span class="p">(</span><span class="s">"%d %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">r</span><span class="p">,</span> <span class="n">buf</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>With the latest MSVC as of this writing (VS 2019), this program prints “<code class="language-plaintext highlighter-rouge">0 hello</code>”. Using <code class="language-plaintext highlighter-rouge">strcpy_s</code> did not make my program any safer than had I just used <code class="language-plaintext highlighter-rouge">strcpy</code>. If anything, it’s <em>less safe</em> due to a false sense of security. Don’t use these functions.</p>
    • Nu chevron_right

      More DLL fun with w64devkit: Go, assembly, and Python

      pubsub.kikeriki.at / null_program · Tuesday, 29 June, 2021 - 21:50 · 31 minutes

    <p>My previous article explained <a href="/blog/2021/05/31/">how to work with dynamic-link libraries (DLLs) using w64devkit</a>. These techniques also apply to other circumstances, including with languages and ecosystems outside of C and C++. In particular, <a href="/blog/2020/05/15/">w64devkit</a> is a great complement to Go and reliably fullfills all the needs of <a href="https://golang.org/cmd/cgo/">cgo</a> — Go’s C interop — and can even bootstrap Go itself. As before, this article is in large part an exercise in capturing practical information I’ve picked up over time.</p> <h3 id="go-bootstrap-and-cgo">Go: bootstrap and cgo</h3> <p>The primary Go implementation, confusingly <a href="https://golang.org/doc/faq#What_compiler_technology_is_used_to_build_the_compilers">named “gc”</a>, is an <a href="/blog/2020/01/21/">incredible piece of software engineering</a>. This is apparent when building the Go toolchain itself, a process that is fast, reliable, easy, and simple. It was originally written in C, but was re-written in Go starting with Go 1.5. The C compiler in w64devkit can build the original C implementation which then can be used to bootstrap any more recent version. It’s so easy that I personally never use official binary releases and always bootstrap from source.</p> <p>You will need the Go 1.4 source, <a href="https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz">go1.4-bootstrap-20171003.tar.gz</a>. This “bootstrap” tarball is the last Go 1.4 release plus a few additional bugfixes. You will also need the source of the actual version of Go you want to use, such as Go 1.16.5 (latest version as of this writing).</p> <p>Start by building Go 1.4 using w64devkit. On Windows, Go is built using a batch script and no special build system is needed. Since it shouldn’t be invoked with the BusyBox ash shell, I use <a href="/blog/2021/02/08/"><code class="language-plaintext highlighter-rouge">cmd.exe</code></a> explicitly.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ tar xf go1.4-bootstrap-20171003.tar.gz $ mv go/ bootstrap $ (cd bootstrap/src/ &amp;&amp; cmd /c make) </code></pre></div></div> <p>In about 30 seconds you’ll have a fully-working Go 1.4 toolchain. Next use it to build the desired toolchain. You can move this new toolchain after it’s built if necessary.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export GOROOT_BOOTSTRAP="$PWD/bootstrap" $ tar xf go1.16.5.src.tar.gz $ (cd go/src/ &amp;&amp; cmd /c make) </code></pre></div></div> <p>At this point you can delete the bootstrap toolchain. You probably also want to put Go on your PATH.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rm -rf bootstrap/ $ printf 'PATH="$PATH;%s/go/bin"\n' "$PWD" &gt;&gt;~/.profile $ source ~/.profile </code></pre></div></div> <p>Not only is Go now available, so is the full power of cgo. (Including <a href="https://dave.cheney.net/2016/01/18/cgo-is-not-go">its costs</a> if used.)</p> <h3 id="vim-suggestions">Vim suggestions</h3> <p>Since w64devkit is oriented so much around Vim, here’s my personal Vim configuration for Go. I don’t need or want fancy plugins, just access to <code class="language-plaintext highlighter-rouge">goimports</code> and a couple of corrections to Vim’s built-in Go support (<code class="language-plaintext highlighter-rouge">[[</code> and <code class="language-plaintext highlighter-rouge">]]</code> navigation). The included <code class="language-plaintext highlighter-rouge">ctags</code> understands Go, so tags navigation works the same as it does with C. <code class="language-plaintext highlighter-rouge">\i</code> saves the current buffer, runs <code class="language-plaintext highlighter-rouge">goimports</code>, and populates the quickfix list with any errors. Similarly <code class="language-plaintext highlighter-rouge">:make</code> invokes <code class="language-plaintext highlighter-rouge">go build</code> and, as expected, populates the quickfix list.</p> <div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>autocmd <span class="nb">FileType</span> <span class="k">go</span> <span class="k">setlocal</span> <span class="nb">makeprg</span><span class="p">=</span><span class="k">go</span>\ build autocmd <span class="nb">FileType</span> <span class="k">go</span> <span class="nb">map</span> <span class="p">&lt;</span><span class="k">silent</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="k">buffer</span><span class="p">&gt;</span> <span class="p">&lt;</span>leader<span class="p">&gt;</span><span class="k">i</span> <span class="se"> \</span> <span class="p">:</span><span class="k">update</span> \<span class="p">|</span> <span class="se"> \</span> <span class="p">:</span><span class="k">cexpr</span> <span class="nb">system</span><span class="p">(</span><span class="s2">"goimports -w "</span> <span class="p">.</span> <span class="nb">expand</span><span class="p">(</span><span class="s2">"%"</span><span class="p">))</span> \<span class="p">|</span> <span class="se"> \</span> <span class="p">:</span><span class="k">silent</span> <span class="k">edit</span><span class="p">&lt;</span><span class="k">cr</span><span class="p">&gt;</span> autocmd <span class="nb">FileType</span> <span class="k">go</span> <span class="nb">map</span> <span class="p">&lt;</span><span class="k">buffer</span><span class="p">&gt;</span> <span class="p">[[</span> <span class="se"> \</span> ?^\<span class="p">(</span>func\\<span class="p">|</span>var\\<span class="p">|</span><span class="nb">type</span>\\<span class="p">|</span><span class="k">import</span>\\<span class="p">|</span>package\<span class="p">)</span>\<span class="p">&gt;&lt;</span><span class="k">cr</span><span class="p">&gt;</span> autocmd <span class="nb">FileType</span> <span class="k">go</span> <span class="nb">map</span> <span class="p">&lt;</span><span class="k">buffer</span><span class="p">&gt;</span> <span class="p">]]</span> <span class="se"> \</span> /^\<span class="p">(</span>func\\<span class="p">|</span>var\\<span class="p">|</span><span class="nb">type</span>\\<span class="p">|</span><span class="k">import</span>\\<span class="p">|</span>package\<span class="p">)</span>\<span class="p">&gt;&lt;</span><span class="k">cr</span><span class="p">&gt;</span> </code></pre></div></div> <p>Go only comes with <code class="language-plaintext highlighter-rouge">gofmt</code> but <code class="language-plaintext highlighter-rouge">goimports</code> is just one command away, so there’s little excuse not to have it:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go install golang.org/x/tools/cmd/goimports@latest </code></pre></div></div> <p>Thanks to GOPROXY, all Go dependencies are accessible without (or before) installing Git, so this tool installation works with nothing more than w64devkit and a bootstrapped Go toolchain.</p> <h3 id="cgo-dlls">cgo DLLs</h3> <p>The intricacies of cgo are beyond the scope of this article, but the gist is that a Go source file contains C source in a comment followed by <code class="language-plaintext highlighter-rouge">import "C"</code>. The imported <code class="language-plaintext highlighter-rouge">C</code> object provides access to C types and functions. Go functions marked with an <code class="language-plaintext highlighter-rouge">//export</code> comment, as well as the commented C code, are accessible to C. The latter means we can use Go to implement a C interface in a DLL, and the caller will have no idea they’re actually talking to Go.</p> <p>To illustrate, here’s an little C interface. To keep it simple, I’ve specifically sidestepped some more complicated issues, particularly involving memory management.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Which DLL am I running?</span> <span class="kt">int</span> <span class="nf">version</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span> <span class="c1">// Generate 64 bits from a CSPRNG.</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="nf">rand64</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span> <span class="c1">// Compute the Euclidean norm.</span> <span class="kt">float</span> <span class="nf">dist</span><span class="p">(</span><span class="kt">float</span> <span class="n">x</span><span class="p">,</span> <span class="kt">float</span> <span class="n">y</span><span class="p">);</span> </code></pre></div></div> <p>Here’s a C implementation which I’m calling “version 1”.</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include &lt;math.h&gt; #include &lt;windows.h&gt; #include &lt;ntsecapi.h&gt; </span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="kt">int</span> <span class="nf">version</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="nf">rand64</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">x</span><span class="p">;</span> <span class="n">RtlGenRandom</span><span class="p">(</span><span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">x</span><span class="p">));</span> <span class="k">return</span> <span class="n">x</span><span class="p">;</span> <span class="p">}</span> <span class="kr">__declspec</span><span class="p">(</span><span class="n">dllexport</span><span class="p">)</span> <span class="kt">float</span> <span class="nf">dist</span><span class="p">(</span><span class="kt">float</span> <span class="n">x</span><span class="p">,</span> <span class="kt">float</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">sqrtf</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="n">x</span> <span class="o">+</span> <span class="n">y</span><span class="o">*</span><span class="n">y</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>As discussed in the previous article, each function is exported using <code class="language-plaintext highlighter-rouge">__declspec</code> so that they’re available for import. As before:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cc -shared -Os -s -o hello1.dll hello1.c </code></pre></div></div> <p>Side note: This could be trivially converted into a C++ implementation just by adding <code class="language-plaintext highlighter-rouge">extern "C"</code> to each declaration. It disables C++ features like name mangling, and follows the C ABI so that the C++ functions appear as C functions. Compiling the C++ DLL is exactly the same.</p> <p>Suppose we wanted to implement this in Go instead of C. We already have all the tools needed to do so. Here’s a Go implementation, “version 2”:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="s">"C"</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"crypto/rand"</span> <span class="s">"encoding/binary"</span> <span class="s">"math"</span> <span class="p">)</span> <span class="c">//export version</span> <span class="k">func</span> <span class="n">version</span><span class="p">()</span> <span class="n">C</span><span class="o">.</span><span class="kt">int</span> <span class="p">{</span> <span class="k">return</span> <span class="m">2</span> <span class="p">}</span> <span class="c">//export rand64</span> <span class="k">func</span> <span class="n">rand64</span><span class="p">()</span> <span class="n">C</span><span class="o">.</span><span class="n">ulonglong</span> <span class="p">{</span> <span class="k">var</span> <span class="n">buf</span> <span class="p">[</span><span class="m">8</span><span class="p">]</span><span class="kt">byte</span> <span class="n">rand</span><span class="o">.</span><span class="n">Read</span><span class="p">(</span><span class="n">buf</span><span class="p">[</span><span class="o">:</span><span class="p">])</span> <span class="n">r</span> <span class="o">:=</span> <span class="n">binary</span><span class="o">.</span><span class="n">LittleEndian</span><span class="o">.</span><span class="n">Uint64</span><span class="p">(</span><span class="n">buf</span><span class="p">[</span><span class="o">:</span><span class="p">])</span> <span class="k">return</span> <span class="n">C</span><span class="o">.</span><span class="n">ulonglong</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="p">}</span> <span class="c">//export dist</span> <span class="k">func</span> <span class="n">dist</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="n">C</span><span class="o">.</span><span class="n">float</span><span class="p">)</span> <span class="n">C</span><span class="o">.</span><span class="n">float</span> <span class="p">{</span> <span class="k">return</span> <span class="n">C</span><span class="o">.</span><span class="n">float</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">Sqrt</span><span class="p">(</span><span class="kt">float64</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="n">x</span> <span class="o">+</span> <span class="n">y</span><span class="o">*</span><span class="n">y</span><span class="p">)))</span> <span class="p">}</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> </code></pre></div></div> <p>Note the use of C types for all arguments and return values. The <code class="language-plaintext highlighter-rouge">main</code> function is required since this is the main package, but it will never be called. The DLL is built like so:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go build -buildmode=c-shared -o hello2.dll hello2.go </code></pre></div></div> <p>Without the <code class="language-plaintext highlighter-rouge">-o</code> option, the DLL will lack an extension. This works fine since it’s mostly only convention on Windows, but it may be confusing without it.</p> <p>What if we need an import library? This will be required when linking with the MSVC toolchain. In the previous article we asked Binutils to generate one using <code class="language-plaintext highlighter-rouge">--out-implib</code>. For Go we have to handle this ourselves via <code class="language-plaintext highlighter-rouge">gendef</code> and <code class="language-plaintext highlighter-rouge">dlltool</code>.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gendef hello2.dll $ dlltool -l hello2.lib -d hello2.def </code></pre></div></div> <p>The only way anyone upgrading would know version 2 was implemented in Go is that the DLL is a lot bigger (a few MB vs. a few kB) since it now contains an entire Go runtime.</p> <h3 id="nasm-assembly-dll">NASM assembly DLL</h3> <p>We could also go the other direction and implement the DLL using plain assembly. It won’t even require linking against a C runtime.</p> <p>w64devkit includes two assemblers: GAS (Binutils) which is used by GCC, and NASM which has <a href="https://elronnd.net/writ/2021-02-13_att-asm.html">friendlier syntax</a>. I prefer the latter whenever possible — exactly why I included NASM in the distribution. So here’s how I implemented “version 3” in NASM assembly.</p> <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">bits</span> <span class="mi">64</span> <span class="nf">section</span> <span class="nv">.text</span> <span class="nf">global</span> <span class="nb">Dl</span><span class="nv">lMainCRTStartup</span> <span class="nf">export</span> <span class="nb">Dl</span><span class="nv">lMainCRTStartup</span> <span class="nl">DllMainCRTStartup:</span> <span class="nf">mov</span> <span class="nb">eax</span><span class="p">,</span> <span class="mi">1</span> <span class="nf">ret</span> <span class="nf">global</span> <span class="nv">version</span> <span class="nf">export</span> <span class="nv">version</span> <span class="nl">version:</span> <span class="nf">mov</span> <span class="nb">eax</span><span class="p">,</span> <span class="mi">3</span> <span class="nf">ret</span> <span class="nf">global</span> <span class="nv">rand64</span> <span class="nf">export</span> <span class="nv">rand64</span> <span class="nl">rand64:</span> <span class="nf">rdrand</span> <span class="nb">rax</span> <span class="nf">ret</span> <span class="nf">global</span> <span class="nb">di</span><span class="nv">st</span> <span class="nf">export</span> <span class="nb">di</span><span class="nv">st</span> <span class="nl">dist:</span> <span class="nf">mulss</span> <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm0</span> <span class="nf">mulss</span> <span class="nv">xmm1</span><span class="p">,</span> <span class="nv">xmm1</span> <span class="nf">addss</span> <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm1</span> <span class="nf">sqrtss</span> <span class="nv">xmm0</span><span class="p">,</span> <span class="nv">xmm0</span> <span class="nf">ret</span> </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">global</code> directive is common in NASM assembly and causes the named symbol to have the external linkage needed when linking the DLL. The <code class="language-plaintext highlighter-rouge">export</code> directive is Windows-specific and is equivalent to <code class="language-plaintext highlighter-rouge">dllexport</code> in C.</p> <p>Every DLL must have an entrypoint, usually named <code class="language-plaintext highlighter-rouge">DllMainCRTStartup</code>. The return value indicates if the DLL successfully loaded. So far this has been handled automatically by the C implementation, but at this low level we must define it explicitly.</p> <p>Here’s how to assemble and link the DLL:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nasm -fwin64 -o hello3.o hello3.s $ ld -shared -s -o hello3.dll hello3.o </code></pre></div></div> <h3 id="call-the-dlls-from-python">Call the DLLs from Python</h3> <p>Python has a nice, built-in C interop, <code class="language-plaintext highlighter-rouge">ctypes</code>, that allows Python to call arbitrary C functions in shared libraries, including DLLs, without writing C to glue it together. To tie this all off, here’s a Python program that loads all of the DLLs above and invokes each of the functions:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">ctypes</span> <span class="k">def</span> <span class="nf">load</span><span class="p">(</span><span class="n">version</span><span class="p">):</span> <span class="n">hello</span> <span class="o">=</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">CDLL</span><span class="p">(</span><span class="sa">f</span><span class="s">"./hello</span><span class="si">{</span><span class="n">version</span><span class="si">}</span><span class="s">.dll"</span><span class="p">)</span> <span class="n">hello</span><span class="p">.</span><span class="n">version</span><span class="p">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">c_int</span> <span class="n">hello</span><span class="p">.</span><span class="n">version</span><span class="p">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">()</span> <span class="n">hello</span><span class="p">.</span><span class="n">dist</span><span class="p">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">c_float</span> <span class="n">hello</span><span class="p">.</span><span class="n">dist</span><span class="p">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">(</span><span class="n">ctypes</span><span class="p">.</span><span class="n">c_float</span><span class="p">,</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">c_float</span><span class="p">)</span> <span class="n">hello</span><span class="p">.</span><span class="n">rand64</span><span class="p">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">c_ulonglong</span> <span class="n">hello</span><span class="p">.</span><span class="n">rand64</span><span class="p">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">()</span> <span class="k">return</span> <span class="n">hello</span> <span class="k">for</span> <span class="n">hello</span> <span class="ow">in</span> <span class="n">load</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">load</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="n">load</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span> <span class="k">print</span><span class="p">(</span><span class="s">"version"</span><span class="p">,</span> <span class="n">hello</span><span class="p">.</span><span class="n">version</span><span class="p">())</span> <span class="k">print</span><span class="p">(</span><span class="s">"rand "</span><span class="p">,</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">hello</span><span class="p">.</span><span class="n">rand64</span><span class="p">():</span><span class="mi">016</span><span class="n">x</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="s">"dist "</span><span class="p">,</span> <span class="n">hello</span><span class="p">.</span><span class="n">dist</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span> </code></pre></div></div> <p>After loading the DLL with <code class="language-plaintext highlighter-rouge">CDLL</code> the program defines each function prototype so that Python knows how to call it. Unfortunately it’s not possible to build Python with w64devkit, so you’ll also need to install the standard CPython distribution in order to run it. Here’s the output:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python finale.py version 1 rand b011ea9bdbde4bdf dist 5.0 version 2 rand f7c86ff06ae3d1a2 dist 5.0 version 3 rand 2a35a05b0482c898 dist 5.0 </code></pre></div></div> <p>That output is the result of four different languages interfacing in one process: C, Go, x86-64 assembly, and Python. Pretty neat if you ask me!</p>
    • chevron_right

      Linker - LD_LIBRARY_PATH

      Stefan · Sunday, 7 June, 2020 - 11:16 · 1 minute

    Ich habe gestern angefangen meine XEP Implementierungen in eine eigene lib zu ziehen. Ich habe wenig Lust immer alles in die verschieden Anwendungen nachzuziehen :-) Dabei bin ich auf folgendes gekommen,...

    ‎Wenn ich eine Anwendung bauen will, bei der die shared lib nicht in den Verzeichnissen von ld liegt, dann kommt es bei der Ausführung zu einem Fehler (kann die shared lib nicht finden). Mit der Variable LD_LIBRARY_PATH kann man das Problem beheben.

    Beispiel Ich baue eine Anwendung mit folgendem Befehl:

     gcc `pkg-config --cflags --libs libstrophe` `gpgme-config --libs --cflags`  -I../ ../.libs/libcxmppx.so -o XEP-0030-disco XEP-0030-disco.c
    

    libcxmppx ist meine lib, die ich lokal entwickel und nicht im System ist. Wenn ich die Anwendung ausführe, dann kommt:

    ./XEP-0030-disco: error while loading shared libraries: libcxmppx.so.0: cannot open shared object file: No such file or directory
    

    Bei der Ausführung der Anwendung guckt (Was auch immer die ELF ausführt) nach der lib, findet die libcxmppx.so aber nicht im System (/etc/ld.so.conf). Also setzte ich die Variable export LD_LIBRARY_PATH=../.libs/. Dann bekomme ich auch meine Anwendung zum laufen:

    ‎./XEP-0030-disco user pwd jid
    Connected!
    Abfrage <iq id="c3d05d07-af84-410d-84f8-58483af7e584" to= ....
    

    #C #LD #XMPP

    • Ha chevron_right

      Du dev et du monitoring : YaPLog

      motius · pubsub.gugod.fr / hashtagueule · Monday, 1 August, 2016 - 22:00 · 7 minutes

    Bonjour à tous ! Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev. Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois ...

    Bonjour à tous !

    Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev.

    Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois quarts du projet, faisons un point sur son avancement, ce qu'il reste à faire, les technos, etc.

    Le cahier des charges

    Le monitoring réseau c'est tout sauf un sujet original. Hyper intéressant, sa mise en pratique évolue au fur et à mesure que les outils, les pratiques, et les protocoles évoluent.

    Vous vous demandez sûrement pourquoi je parle de développement dans ce cas. Il y a certes de nombreux programmes permettant de faire pas mal de choses. Voici pourquoi.

    • La meilleure raison : je n'ai pas accès (question de contrat avec l'opérateur réseau) aux routeurs, et il faudrait que j'aie la main sur une centaine de routeurs ;
    • la deuxième meilleure raison : on m'a demandé une tâche spécifique (du reporting quotidien, entre autres). Je ne dis pas que ce n'est pas possible avec un Nagios-Cacti ou un Munin, mais je n'aurais pas commencé ce qu'on m'a demandé (voir ce qui suit) dans le temps imparti ;
    • une raison pratique : on ne change pas la configuration d'une centaine de routeurs à travers le monde en 40 jours. C'est à peine suffisant pour traverser un désert.

    Que veut-on savoir ? La question initiale est "quand est-ce que ma ligne est saturée" ce qui est déjà visible sur les logs du FAI (Nagios-Cacti, exporté en petites images JPG) mais n'est pas suffisant pour plusieurs raisons :

    • on n'a pas de système d'alerte (facile à mettre en œuvre) ;
    • on n'a pas de données exploitables plus avant (l'image JPG, c'est pas beaucoup) ;

    On veut donc savoir quand est-ce que la ligne est saturée. C'est-à-dire, a priori, quand est-ce que la ligne atteint sont débit maximal.

    Le but suivant est de déterminer si une saturation est gênante. Cela comprend plusieurs facteurs :

    • l'heure : une saturation entre 2h et 4h du mat' parce qu'on fait des backup, ça ne gêne personne (ça pourrait, mais pas dans ce cas) ;
    • une saturation d'une seconde dans la journée n'est pas grave. De même, 250 saturations quasi-instantanées, réparties de 8h à 20h ne gênent pas le travail ;

    Une fois qu'on a déterminé si une saturation est gênante, on veut savoir qui elle gêne et pourquoi, afin de voir si on peut faire quelque chose pour améliorer l'utilisation des ressources.

    • Par exemple, lorsqu'un site est saturé, cela gêne-t-il 1 ou 50 personnes ? Dans le premier cas, la personne est peut-être trop gourmande en ressources, dans le second, le lien est peut-être mal dimensionné.
    • Certains processus peuvent être automatisés la nuit, certaines pratiques changées : backup du travail pendant la pause de midi et la nuit, passer de FTP à rsync pour les backup, ou au moins un système incrémental plutôt que full

    Implémentation

    Comme je sens que vous êtes des warriors,

    cyclops_warrior_concept_by_masterfulmind-d4vi7mx

    Un warrior, probablement.

    je vous propose de rentrer dans le vif du sujet avec les détails techniques.

    Unzip

    Je pars avec une archive zip, gentiment déposée par mon FAI, dans laquelle il y a des logs au format nfcapd, défini par Cisco.

    Nfdump

    Nfcapd est un fichier binaire, pas très comestible pour l'œil humain. J'ai donc trouvé nfdump, un programme qui permet de transformer les logs binaires en texte ASCII. ça ressemble à peu près à ça :

    2016-09-21 21:47:42.360     0.000 TCP         192.168.5.3:445   ->    192.168.5.249:56011        1       52     1

    csvify

    C'est mieux, mais c'est toujours pas optimal. Il y a notamment trois problèmes :

    • la taille du fichier ASCII par rapport au binaire nfcapd est importante, environ 9.5 fois plus ;
    • le texte ASCII est très irrégulier, le nombre d'espaces est très variable ;
    • sont inutiles :
      • la flèche ;
      • le protocole aussi dans la majorité des cas (si on fait des calculs de poids en octets) ;
      • les ports, sauf éventuellement dans le cas d'un téléchargement, le port source pour déterminer le type de trafic (HTTP : 80, POP/IMAP, IRC : 6667/6697...) ;
      • le "1" à la toute fin, qui désigne le nombre de flow, toujours à 1 dans mon cas, vu les options passées à nfdump.

    J'ai donc créé un préparser en C (il s'agit en fait d'une spécification flex, puis flex génère le code C correspondant, à l'aide d'automates) (il y en a deux, l'un d'entre eux affiche des couleurs, utile pour visualiser, interdit pour l'utilisation en production, parce qu'il génère bien trop de caractères d'échappement).

    En moyenne, mon préparser sort un fichier au format CSV qui fait 70% de la taille du fichier d'entrée, et est beaucoup plus normalisé. Il ne supprime cependant pas beaucoup d'informations (la flèche seulement).

    Parse/Somme et plus si affinités

    Maintenant qu'on s'est facilité la tâche, il ne reste plus qu'à analyser le tout.

    C'est le but du programme C++ suivant, qui va itérer sur les entrées du CSV qu'on a produit, et ranger les paquets aux bons endroits.

    Enfin, en théorie. Parce qu'on se retrouve devant quelques petits problèmes :

    • Il y a des paquets qui on une durée de passage au travers du réseau nulle, et pour faire des graphs, c'est pas top. Il faut donc gérer cela.
      • On attribue une valeur faible à la durée de transmission.
      • On effectuera une moyenne (moyenne simple ou mieux, implémentation de l'algorithme SMA (ici en fr) pour lisser la courbe obtenue.
    • Même si le C/C++ est rapide, la partie lecture du fichier sur disque est très rapide, la partie traitement est lente, la partie écriture des résultats sur disque est rapide.
      • On adopte donc l'architecture suivante pour le programme C++ :

    C++ n-multithread

    Architecture du programme C++

    Le thread lecteur en haut lit depuis le disque à gauche, range les éléments dans la std::queue fileQ protégée par un mutex (fileQmutex, je suis d'une originalité à toute épreuve), tandis que les n threads workers vont travailler à générer la donnée, puis vont passer leur résultat à la liste de résultats resQD. Finalement le thread écrivain va réunir la liste de résultats, et l'écrire sur disque (à droite).

    Comme vous le voyez, beaucoup de choses ont été faites, beaucoup de choix, et j'en passe de très nombreux (j'en mettrai en PS), et il reste beaucoup à accomplir, notamment :

    • la doc : pour la contiuation du projet ;
    • le manuel d'uilisation pour l'exploitation ;
    • un How-to pour installer/améliorer le bidule ;
    • utiliser la classe Traffic() du programme C++ pour effectivement ranger les paquets dans des types de Traffic (par port pour le protocole, par IP source pour les utilisateurs les plus gourmands, par IP destination pour les serveurs les plus consultés/les plus consommateurs de ressource, et pour déterminer combien d'utilisateurs sont effectivement gênés lors d'une saturation).

    Des graphs. For free.

    Une fois que l'on a écrit le CSV, il ne reste qu'à faire le graph. le programme gnuplot peut faire ça très bien.

    Un chef d'orchestre

    Tout cela est opéré depuis un script bash.

    Voilà, j'espère que ça vous a plu, si ça vous amuse, le projet est libre, disponible ici. Comme d'habitude n'hésitez pas à commenter et à bientôt !

     

    Motius

    PS : j'ai appris plein de choses, mais qui sont de l'ordre du détail pour ce projet, alors je vais en lister quelques unes ici :

    • mes CPU aiment travailler de manière symétrique. quand je mets n workers, je gagne plus en pourcentage de vitesse en passant d'un nombre impair à un nombre pair. Les chiffres :
      • de 1 à 2 CPU : 3.37 fois plus rapide (le programme n-multithread est plus lent que ne l'était le programme monothread équivalent si l'on ne met qu'un seul thread worker, n=1) ;
      • de 2 à 3 CPU : 1.12 fois ;
      • de 3 à 4 CPU : 1.67 fois ;
    • les regex sont assez lentes, il y a potentiellement une bonne optimisation possible en bidouillant des C-strings au lieu des std::string du C++ (le pré-parser C est beaucoup beaucoup plus rapide que le programme C++, aussi parce que son travail ne consiste qu'en des substitutions/suppressions), mais le temps de développement serait plus long, et le risque d'overflow/segfault possible.
    • At chevron_right

      Du dev et du monitoring : YaPLog

      motius · pubsub.gugod.fr / atomtest · Monday, 1 August, 2016 - 22:00 · 7 minutes

    Bonjour à tous ! Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev. Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois ...

    Bonjour à tous !

    Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev.

    Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois quarts du projet, faisons un point sur son avancement, ce qu'il reste à faire, les technos, etc.

    Le cahier des charges

    Le monitoring réseau c'est tout sauf un sujet original. Hyper intéressant, sa mise en pratique évolue au fur et à mesure que les outils, les pratiques, et les protocoles évoluent.

    Vous vous demandez sûrement pourquoi je parle de développement dans ce cas. Il y a certes de nombreux programmes permettant de faire pas mal de choses. Voici pourquoi.

    • La meilleure raison : je n'ai pas accès (question de contrat avec l'opérateur réseau) aux routeurs, et il faudrait que j'aie la main sur une centaine de routeurs ;
    • la deuxième meilleure raison : on m'a demandé une tâche spécifique (du reporting quotidien, entre autres). Je ne dis pas que ce n'est pas possible avec un Nagios-Cacti ou un Munin, mais je n'aurais pas commencé ce qu'on m'a demandé (voir ce qui suit) dans le temps imparti ;
    • une raison pratique : on ne change pas la configuration d'une centaine de routeurs à travers le monde en 40 jours. C'est à peine suffisant pour traverser un désert.

    Que veut-on savoir ? La question initiale est "quand est-ce que ma ligne est saturée" ce qui est déjà visible sur les logs du FAI (Nagios-Cacti, exporté en petites images JPG) mais n'est pas suffisant pour plusieurs raisons :

    • on n'a pas de système d'alerte (facile à mettre en œuvre) ;
    • on n'a pas de données exploitables plus avant (l'image JPG, c'est pas beaucoup) ;

    On veut donc savoir quand est-ce que la ligne est saturée. C'est-à-dire, a priori, quand est-ce que la ligne atteint sont débit maximal.

    Le but suivant est de déterminer si une saturation est gênante. Cela comprend plusieurs facteurs :

    • l'heure : une saturation entre 2h et 4h du mat' parce qu'on fait des backup, ça ne gêne personne (ça pourrait, mais pas dans ce cas) ;
    • une saturation d'une seconde dans la journée n'est pas grave. De même, 250 saturations quasi-instantanées, réparties de 8h à 20h ne gênent pas le travail ;

    Une fois qu'on a déterminé si une saturation est gênante, on veut savoir qui elle gêne et pourquoi, afin de voir si on peut faire quelque chose pour améliorer l'utilisation des ressources.

    • Par exemple, lorsqu'un site est saturé, cela gêne-t-il 1 ou 50 personnes ? Dans le premier cas, la personne est peut-être trop gourmande en ressources, dans le second, le lien est peut-être mal dimensionné.
    • Certains processus peuvent être automatisés la nuit, certaines pratiques changées : backup du travail pendant la pause de midi et la nuit, passer de FTP à rsync pour les backup, ou au moins un système incrémental plutôt que full

    Implémentation

    Comme je sens que vous êtes des warriors,

    cyclops_warrior_concept_by_masterfulmind-d4vi7mx

    Un warrior, probablement.

    je vous propose de rentrer dans le vif du sujet avec les détails techniques.

    Unzip

    Je pars avec une archive zip, gentiment déposée par mon FAI, dans laquelle il y a des logs au format nfcapd, défini par Cisco.

    Nfdump

    Nfcapd est un fichier binaire, pas très comestible pour l'œil humain. J'ai donc trouvé nfdump, un programme qui permet de transformer les logs binaires en texte ASCII. ça ressemble à peu près à ça :

    2016-09-21 21:47:42.360     0.000 TCP         192.168.5.3:445   ->    192.168.5.249:56011        1       52     1

    csvify

    C'est mieux, mais c'est toujours pas optimal. Il y a notamment trois problèmes :

    • la taille du fichier ASCII par rapport au binaire nfcapd est importante, environ 9.5 fois plus ;
    • le texte ASCII est très irrégulier, le nombre d'espaces est très variable ;
    • sont inutiles :
      • la flèche ;
      • le protocole aussi dans la majorité des cas (si on fait des calculs de poids en octets) ;
      • les ports, sauf éventuellement dans le cas d'un téléchargement, le port source pour déterminer le type de trafic (HTTP : 80, POP/IMAP, IRC : 6667/6697...) ;
      • le "1" à la toute fin, qui désigne le nombre de flow, toujours à 1 dans mon cas, vu les options passées à nfdump.

    J'ai donc créé un préparser en C (il s'agit en fait d'une spécification flex, puis flex génère le code C correspondant, à l'aide d'automates) (il y en a deux, l'un d'entre eux affiche des couleurs, utile pour visualiser, interdit pour l'utilisation en production, parce qu'il génère bien trop de caractères d'échappement).

    En moyenne, mon préparser sort un fichier au format CSV qui fait 70% de la taille du fichier d'entrée, et est beaucoup plus normalisé. Il ne supprime cependant pas beaucoup d'informations (la flèche seulement).

    Parse/Somme et plus si affinités

    Maintenant qu'on s'est facilité la tâche, il ne reste plus qu'à analyser le tout.

    C'est le but du programme C++ suivant, qui va itérer sur les entrées du CSV qu'on a produit, et ranger les paquets aux bons endroits.

    Enfin, en théorie. Parce qu'on se retrouve devant quelques petits problèmes :

    • Il y a des paquets qui on une durée de passage au travers du réseau nulle, et pour faire des graphs, c'est pas top. Il faut donc gérer cela.
      • On attribue une valeur faible à la durée de transmission.
      • On effectuera une moyenne (moyenne simple ou mieux, implémentation de l'algorithme SMA (ici en fr) pour lisser la courbe obtenue.
    • Même si le C/C++ est rapide, la partie lecture du fichier sur disque est très rapide, la partie traitement est lente, la partie écriture des résultats sur disque est rapide.
      • On adopte donc l'architecture suivante pour le programme C++ :

    C++ n-multithread

    Architecture du programme C++

    Le thread lecteur en haut lit depuis le disque à gauche, range les éléments dans la std::queue fileQ protégée par un mutex (fileQmutex, je suis d'une originalité à toute épreuve), tandis que les n threads workers vont travailler à générer la donnée, puis vont passer leur résultat à la liste de résultats resQD. Finalement le thread écrivain va réunir la liste de résultats, et l'écrire sur disque (à droite).

    Comme vous le voyez, beaucoup de choses ont été faites, beaucoup de choix, et j'en passe de très nombreux (j'en mettrai en PS), et il reste beaucoup à accomplir, notamment :

    • la doc : pour la contiuation du projet ;
    • le manuel d'uilisation pour l'exploitation ;
    • un How-to pour installer/améliorer le bidule ;
    • utiliser la classe Traffic() du programme C++ pour effectivement ranger les paquets dans des types de Traffic (par port pour le protocole, par IP source pour les utilisateurs les plus gourmands, par IP destination pour les serveurs les plus consultés/les plus consommateurs de ressource, et pour déterminer combien d'utilisateurs sont effectivement gênés lors d'une saturation).

    Des graphs. For free.

    Une fois que l'on a écrit le CSV, il ne reste qu'à faire le graph. le programme gnuplot peut faire ça très bien.

    Un chef d'orchestre

    Tout cela est opéré depuis un script bash.

    Voilà, j'espère que ça vous a plu, si ça vous amuse, le projet est libre, disponible ici. Comme d'habitude n'hésitez pas à commenter et à bientôt !

     

    Motius

    PS : j'ai appris plein de choses, mais qui sont de l'ordre du détail pour ce projet, alors je vais en lister quelques unes ici :

    • mes CPU aiment travailler de manière symétrique. quand je mets n workers, je gagne plus en pourcentage de vitesse en passant d'un nombre impair à un nombre pair. Les chiffres :
      • de 1 à 2 CPU : 3.37 fois plus rapide (le programme n-multithread est plus lent que ne l'était le programme monothread équivalent si l'on ne met qu'un seul thread worker, n=1) ;
      • de 2 à 3 CPU : 1.12 fois ;
      • de 3 à 4 CPU : 1.67 fois ;
    • les regex sont assez lentes, il y a potentiellement une bonne optimisation possible en bidouillant des C-strings au lieu des std::string du C++ (le pré-parser C est beaucoup beaucoup plus rapide que le programme C++, aussi parce que son travail ne consiste qu'en des substitutions/suppressions), mais le temps de développement serait plus long, et le risque d'overflow/segfault possible.